diff --git a/ext/crates/algebra/Cargo.toml b/ext/crates/algebra/Cargo.toml index 9a45a269fb..1daa876907 100644 --- a/ext/crates/algebra/Cargo.toml +++ b/ext/crates/algebra/Cargo.toml @@ -15,6 +15,7 @@ bivec = { path = "../bivec" } fp = { path = "../fp", default-features = false } maybe-rayon = { path = "../maybe-rayon" } once = { path = "../once" } +sseq = { path = "../sseq", default-features = false } anyhow = "1.0.98" auto_impl = "1.3.0" @@ -36,8 +37,8 @@ rstest = "0.25.0" [features] default = ["odd-primes"] cache-multiplication = [] -concurrent = ["fp/concurrent", "maybe-rayon/concurrent"] -odd-primes = ["fp/odd-primes"] +concurrent = ["fp/concurrent", "maybe-rayon/concurrent", "sseq/concurrent"] +odd-primes = ["fp/odd-primes", "sseq/odd-primes"] [[bench]] name = "milnor" diff --git a/ext/crates/algebra/src/algebra/adem_algebra.rs b/ext/crates/algebra/src/algebra/adem_algebra.rs index 100f42a506..37098be91c 100644 --- a/ext/crates/algebra/src/algebra/adem_algebra.rs +++ b/ext/crates/algebra/src/algebra/adem_algebra.rs @@ -12,6 +12,7 @@ use fp::{ use itertools::Itertools; use once::OnceVec; use rustc_hash::FxHashMap as HashMap; +use sseq::coordinates::MultiDegree; #[cfg(doc)] use crate::algebra::SteenrodAlgebra; @@ -222,7 +223,8 @@ impl Algebra for AdemAlgebra { .collect() } - fn compute_basis(&self, max_degree: i32) { + fn compute_basis(&self, max_degree: impl Into>) { + let max_degree = i32::from(max_degree.into()); if self.generic { self.generate_basis_generic(max_degree); self.generate_basis_element_to_index_map(max_degree); @@ -238,7 +240,8 @@ impl Algebra for AdemAlgebra { } } - fn dimension(&self, degree: i32) -> usize { + fn dimension(&self, degree: impl Into>) -> usize { + let degree = i32::from(degree.into()); if degree < 0 { 0 } else { @@ -250,24 +253,27 @@ impl Algebra for AdemAlgebra { &self, result: FpSliceMut, coeff: u32, - r_degree: i32, + r_degree: impl Into>, r_index: usize, - s_degree: i32, + s_degree: impl Into>, s_index: usize, ) { self.multiply_inner( result, coeff, - r_degree, + i32::from(r_degree.into()), r_index, - s_degree, + i32::from(s_degree.into()), s_index, i32::MAX, ); } - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String { - format!("{}", self.basis_element_from_index(degree, idx)) + fn basis_element_to_string(&self, degree: impl Into>, idx: usize) -> String { + format!( + "{}", + self.basis_element_from_index(i32::from(degree.into()), idx) + ) } fn basis_element_from_string(&self, mut elt: &str) -> Option<(i32, usize)> { diff --git a/ext/crates/algebra/src/algebra/algebra_trait.rs b/ext/crates/algebra/src/algebra/algebra_trait.rs index cad1ac0f5f..0812c8f411 100644 --- a/ext/crates/algebra/src/algebra/algebra_trait.rs +++ b/ext/crates/algebra/src/algebra/algebra_trait.rs @@ -6,6 +6,7 @@ use fp::{ vector::{FpSlice, FpSliceMut}, }; use itertools::Itertools; +use sseq::coordinates::MultiDegree; /// A graded algebra over $\mathbb{F}_p$. /// @@ -20,8 +21,16 @@ use itertools::Itertools; /// this function before performing other operations at that degree. /// /// Algebras may have a distinguished set of generators; see [`GeneratedAlgebra`]. -#[enum_dispatch] -pub trait Algebra: std::fmt::Display + Send + Sync + 'static { +/// +/// # Grading +/// The trait is generic over the number of gradings `N` (defaulting to `1`, the singly-graded +/// case). Degrees are [`MultiDegree`]; every degree *input* is taken as `impl Into>` +/// so callers in the singly-graded world keep passing bare `i32`s (via `From for +/// MultiDegree<1>`). `MultiDegree<2>` is a bidegree, used by `Ext` (see `ext::ext_algebra`). +/// +/// `Algebra` cannot use `#[enum_dispatch]` because it is now generic; `SteenrodAlgebra` dispatches +/// it by hand (see `dispatch_steenrod!`). +pub trait Algebra: std::fmt::Display + Send + Sync + 'static { /// A name for the algebra to use in serialization operations. This defaults to "" for algebras /// that don't care about this problem. fn prefix(&self) -> &str { @@ -49,10 +58,10 @@ pub trait Algebra: std::fmt::Display + Send + Sync + 'static { /// /// This function must be idempotent and cheap to call again with the /// same argument. - fn compute_basis(&self, degree: i32); + fn compute_basis(&self, degree: impl Into>); /// Returns the dimension of the algebra in degree `degree`. - fn dimension(&self, degree: i32) -> usize; + fn dimension(&self, degree: impl Into>) -> usize; /// Computes the product `r * s` of two basis elements, and adds the /// result to `result`. @@ -62,9 +71,9 @@ pub trait Algebra: std::fmt::Display + Send + Sync + 'static { &self, result: FpSliceMut, coeff: u32, - r_degree: i32, + r_degree: impl Into>, r_idx: usize, - s_degree: i32, + s_degree: impl Into>, s_idx: usize, ); @@ -76,12 +85,14 @@ pub trait Algebra: std::fmt::Display + Send + Sync + 'static { &self, mut result: FpSliceMut, coeff: u32, - r_degree: i32, + r_degree: impl Into>, r_idx: usize, - s_degree: i32, + s_degree: impl Into>, s: FpSlice, ) { let p = self.prime(); + let r_degree = r_degree.into(); + let s_degree = s_degree.into(); for (i, v) in s.iter_nonzero() { self.multiply_basis_elements( result.copy(), @@ -102,12 +113,14 @@ pub trait Algebra: std::fmt::Display + Send + Sync + 'static { &self, mut result: FpSliceMut, coeff: u32, - r_degree: i32, + r_degree: impl Into>, r: FpSlice, - s_degree: i32, + s_degree: impl Into>, s_idx: usize, ) { let p = self.prime(); + let r_degree = r_degree.into(); + let s_degree = s_degree.into(); for (i, v) in r.iter_nonzero() { self.multiply_basis_elements( result.copy(), @@ -128,12 +141,14 @@ pub trait Algebra: std::fmt::Display + Send + Sync + 'static { &self, mut result: FpSliceMut, coeff: u32, - r_degree: i32, + r_degree: impl Into>, r: FpSlice, - s_degree: i32, + s_degree: impl Into>, s: FpSlice, ) { let p = self.prime(); + let r_degree = r_degree.into(); + let s_degree = s_degree.into(); for (i, v) in s.iter_nonzero() { self.multiply_element_by_basis_element( result.copy(), @@ -158,7 +173,7 @@ pub trait Algebra: std::fmt::Display + Send + Sync + 'static { } /// Converts a basis element into a string for display. - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String; + fn basis_element_to_string(&self, degree: impl Into>, idx: usize) -> String; /// Non-panicking variant of [`Self::basis_element_to_string`]. Returns `None` /// when `degree` is negative or `idx` is out of range for that degree, instead @@ -167,8 +182,13 @@ pub trait Algebra: std::fmt::Display + Send + Sync + 'static { /// These are the usual failure conditions. An implementation of /// [`Self::basis_element_to_string`] that can fail for other reasons should override both /// that method and this one, keeping them consistent. - fn try_basis_element_to_string(&self, degree: i32, idx: usize) -> Option { - if degree < 0 { + fn try_basis_element_to_string( + &self, + degree: impl Into>, + idx: usize, + ) -> Option { + let degree = degree.into(); + if degree.t() < 0 { return None; } self.compute_basis(degree); @@ -187,7 +207,8 @@ pub trait Algebra: std::fmt::Display + Send + Sync + 'static { fn basis_element_from_string(&self, elt: &str) -> Option<(i32, usize)>; /// Converts a general element into a string for display. - fn element_to_string(&self, degree: i32, element: FpSlice) -> String { + fn element_to_string(&self, degree: impl Into>, element: FpSlice) -> String { + let degree = degree.into(); let result = element .iter_nonzero() .map(|(idx, value)| { diff --git a/ext/crates/algebra/src/algebra/field.rs b/ext/crates/algebra/src/algebra/field.rs index 6f18ebfe4a..6e9e8c2e3e 100644 --- a/ext/crates/algebra/src/algebra/field.rs +++ b/ext/crates/algebra/src/algebra/field.rs @@ -4,6 +4,7 @@ use fp::{ prime::ValidPrime, vector::{FpSlice, FpSliceMut}, }; +use sseq::coordinates::MultiDegree; use crate::algebra::{Algebra, Bialgebra}; @@ -34,19 +35,19 @@ impl Algebra for Field { self.prime } - fn compute_basis(&self, _degree: i32) {} + fn compute_basis(&self, _degree: impl Into>) {} - fn dimension(&self, degree: i32) -> usize { - usize::from(degree == 0) + fn dimension(&self, degree: impl Into>) -> usize { + usize::from(i32::from(degree.into()) == 0) } fn multiply_basis_elements( &self, mut result: FpSliceMut, coeff: u32, - _r_degree: i32, + _r_degree: impl Into>, _r_idx: usize, - _s_degree: i32, + _s_degree: impl Into>, _s_idx: usize, ) { result.add_basis_element(0, coeff) @@ -56,13 +57,13 @@ impl Algebra for Field { vec![] } - fn basis_element_to_string(&self, degree: i32, _idx: usize) -> String { - assert!(degree == 0); + fn basis_element_to_string(&self, degree: impl Into>, _idx: usize) -> String { + assert!(i32::from(degree.into()) == 0); "1".to_string() } - fn element_to_string(&self, degree: i32, element: FpSlice) -> String { - assert!(degree == 0); + fn element_to_string(&self, degree: impl Into>, element: FpSlice) -> String { + assert!(i32::from(degree.into()) == 0); format!("{}", element.entry(0)) } diff --git a/ext/crates/algebra/src/algebra/milnor_algebra.rs b/ext/crates/algebra/src/algebra/milnor_algebra.rs index 07b3bd35f8..ab24f9d2a3 100644 --- a/ext/crates/algebra/src/algebra/milnor_algebra.rs +++ b/ext/crates/algebra/src/algebra/milnor_algebra.rs @@ -8,6 +8,7 @@ use itertools::Itertools; use once::OnceVec; use rustc_hash::FxHashMap as HashMap; use serde::{Deserialize, Serialize}; +use sseq::coordinates::MultiDegree; use crate::algebra::{Algebra, Bialgebra, GeneratedAlgebra, UnstableAlgebra, combinatorics}; @@ -416,7 +417,8 @@ impl Algebra for MilnorAlgebra { .collect() } - fn compute_basis(&self, max_degree: i32) { + fn compute_basis(&self, max_degree: impl Into>) { + let max_degree = i32::from(max_degree.into()); self.compute_ppart(max_degree); if self.generic() { @@ -470,7 +472,8 @@ impl Algebra for MilnorAlgebra { } } - fn dimension(&self, degree: i32) -> usize { + fn dimension(&self, degree: impl Into>) -> usize { + let degree = i32::from(degree.into()); if degree < 0 { return 0; } @@ -482,16 +485,16 @@ impl Algebra for MilnorAlgebra { &self, result: FpSliceMut, coef: u32, - r_degree: i32, + r_degree: impl Into>, r_idx: usize, - s_degree: i32, + s_degree: impl Into>, s_idx: usize, ) { self.multiply( result, coef, - self.basis_element_from_index(r_degree, r_idx), - self.basis_element_from_index(s_degree, s_idx), + self.basis_element_from_index(i32::from(r_degree.into()), r_idx), + self.basis_element_from_index(i32::from(s_degree.into()), s_idx), ); } @@ -500,11 +503,13 @@ impl Algebra for MilnorAlgebra { &self, mut result: FpSliceMut, coef: u32, - r_degree: i32, + r_degree: impl Into>, r_idx: usize, - s_degree: i32, + s_degree: impl Into>, s_idx: usize, ) { + let r_degree = i32::from(r_degree.into()); + let s_degree = i32::from(s_degree.into()); result.add( self.multiplication_table[r_degree as usize][s_degree as usize][r_idx][s_idx] .as_slice(), @@ -516,12 +521,14 @@ impl Algebra for MilnorAlgebra { &self, mut result: FpSliceMut, coeff: u32, - r_degree: i32, + r_degree: impl Into>, r_idx: usize, - s_degree: i32, + s_degree: impl Into>, s: FpSlice, ) { let p = self.prime(); + let r_degree = i32::from(r_degree.into()); + let s_degree = i32::from(s_degree.into()); let r = self.basis_element_from_index(r_degree, r_idx); PPartAllocation::with_local(|mut allocation| { for (i, v) in s.iter_nonzero() { @@ -542,11 +549,13 @@ impl Algebra for MilnorAlgebra { &self, mut res: FpSliceMut, coef: u32, - r_deg: i32, + r_deg: impl Into>, r: FpSlice, - s_deg: i32, + s_deg: impl Into>, s: FpSlice, ) { + let r_deg = i32::from(r_deg.into()); + let s_deg = i32::from(s_deg.into()); PPartAllocation::with_local(|mut allocation| { for (i, c) in r.iter_nonzero() { allocation = self.multiply_basis_by_element_with_allocation( @@ -562,8 +571,11 @@ impl Algebra for MilnorAlgebra { }) } - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String { - format!("{}", self.basis_element_from_index(degree, idx)) + fn basis_element_to_string(&self, degree: impl Into>, idx: usize) -> String { + format!( + "{}", + self.basis_element_from_index(i32::from(degree.into()), idx) + ) } fn basis_element_from_string(&self, elt: &str) -> Option<(i32, usize)> { diff --git a/ext/crates/algebra/src/algebra/steenrod_algebra.rs b/ext/crates/algebra/src/algebra/steenrod_algebra.rs index 04c65a7e4e..0a502dfec8 100644 --- a/ext/crates/algebra/src/algebra/steenrod_algebra.rs +++ b/ext/crates/algebra/src/algebra/steenrod_algebra.rs @@ -7,6 +7,7 @@ use fp::{ }; use serde::Deserialize; use serde_json::Value; +use sseq::coordinates::MultiDegree; use crate::{ algebra::{AdemAlgebra, Algebra, Bialgebra, GeneratedAlgebra, MilnorAlgebra, UnstableAlgebra}, @@ -53,7 +54,10 @@ impl std::str::FromStr for AlgebraType { } #[allow(clippy::large_enum_variant)] -#[enum_dispatch::enum_dispatch(Algebra, Bialgebra, GeneratedAlgebra, UnstableAlgebra)] +// `Algebra` is now generic (`Algebra`), which `enum_dispatch` cannot handle, so it is +// dispatched by hand below via `dispatch_steenrod!`. The remaining (non-generic) traits still use +// `enum_dispatch`. +#[enum_dispatch::enum_dispatch(Bialgebra, GeneratedAlgebra, UnstableAlgebra)] pub enum SteenrodAlgebra { AdemAlgebra(AdemAlgebra), MilnorAlgebra(MilnorAlgebra), @@ -142,6 +146,22 @@ macro_rules! dispatch_steenrod { }; } +impl Algebra for SteenrodAlgebra { + dispatch_steenrod! { + fn prime(&self) -> ValidPrime; + fn prefix(&self) -> &str; + fn magic(&self) -> u32; + fn compute_basis(&self, degree: impl Into>); + fn dimension(&self, degree: impl Into>) -> usize; + fn multiply_basis_elements(&self, result: FpSliceMut, coeff: u32, r_degree: impl Into>, r_idx: usize, s_degree: impl Into>, s_idx: usize); + fn multiply_basis_element_by_element(&self, result: FpSliceMut, coeff: u32, r_degree: impl Into>, r_idx: usize, s_degree: impl Into>, s: FpSlice); + fn multiply_element_by_element(&self, result: FpSliceMut, coeff: u32, r_degree: impl Into>, r: FpSlice, s_degree: impl Into>, s: FpSlice); + fn default_filtration_one_products(&self) -> Vec<(String, i32, usize)>; + fn basis_element_to_string(&self, degree: impl Into>, idx: usize) -> String; + fn basis_element_from_string(&self, elt: &str) -> Option<(i32, usize)>; + } +} + impl PairAlgebra for AdemAlgebra { type Element = crate::pair_algebra::MilnorPairElement; diff --git a/ext/crates/algebra/src/module/finite_dimensional_module.rs b/ext/crates/algebra/src/module/finite_dimensional_module.rs index 58fed33b6f..84fae59999 100644 --- a/ext/crates/algebra/src/module/finite_dimensional_module.rs +++ b/ext/crates/algebra/src/module/finite_dimensional_module.rs @@ -5,10 +5,11 @@ use bivec::BiVec; use fp::vector::{FpSliceMut, FpVector}; use serde::Deserialize; use serde_json::{json, value::Value}; +use sseq::coordinates::MultiDegree; use crate::{ algebra::{Algebra, GeneratedAlgebra}, - module::{Module, ModuleFailedRelationError, ZeroModule}, + module::{Module, ModuleExt, ModuleFailedRelationError, ZeroModule}, }; pub struct FiniteDimensionalModule { @@ -137,9 +138,10 @@ impl Module for FiniteDimensionalModule { i32::MAX } - fn compute_basis(&self, _degree: i32) {} + fn compute_basis_multi(&self, _degree: MultiDegree<1>) {} - fn dimension(&self, degree: i32) -> usize { + fn dimension_multi(&self, degree: MultiDegree<1>) -> usize { + let degree = i32::from(degree); if degree < self.graded_dimension.min_degree() { return 0; } @@ -149,19 +151,22 @@ impl Module for FiniteDimensionalModule { self.graded_dimension[degree] } - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String { + fn basis_element_to_string_multi(&self, degree: MultiDegree<1>, idx: usize) -> String { + let degree = i32::from(degree); self.gen_names[degree][idx].clone() } - fn act_on_basis( + fn act_on_basis_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree<1>, mod_index: usize, ) { + let op_degree = i32::from(op_degree); + let mod_degree = i32::from(mod_degree); assert!(op_index < self.algebra().dimension(op_degree)); assert!(mod_index < self.dimension(mod_degree)); let output_dimension = self.dimension(mod_degree + op_degree); diff --git a/ext/crates/algebra/src/module/finitely_presented_module.rs b/ext/crates/algebra/src/module/finitely_presented_module.rs index d8af3d11e1..e61cf7d0c9 100644 --- a/ext/crates/algebra/src/module/finitely_presented_module.rs +++ b/ext/crates/algebra/src/module/finitely_presented_module.rs @@ -4,11 +4,12 @@ use fp::vector::{FpSliceMut, FpVector}; use itertools::Itertools; use once::OnceBiVec; use serde_json::Value; +use sseq::coordinates::MultiDegree; use crate::{ algebra::Algebra, module::{ - FreeModule, Module, ZeroModule, + FreeModule, Module, ModuleExt, ZeroModule, homomorphism::{FreeModuleHomomorphism, ModuleHomomorphism}, }, }; @@ -185,7 +186,8 @@ impl Module for FinitelyPresentedModule { self.generators.max_computed_degree() } - fn compute_basis(&self, degree: i32) { + fn compute_basis_multi(&self, degree: MultiDegree<1>) { + let degree = i32::from(degree); self.generators.extend_by_zero(degree); self.relations.extend_by_zero(degree); self.map.compute_auxiliary_data_through_degree(degree); @@ -209,20 +211,23 @@ impl Module for FinitelyPresentedModule { }); } - fn dimension(&self, degree: i32) -> usize { + fn dimension_multi(&self, degree: MultiDegree<1>) -> usize { + let degree = i32::from(degree); assert!(degree >= self.min_degree); self.index_table[degree].fp_idx_to_gen_idx.len() } - fn act_on_basis( + fn act_on_basis_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree<1>, mod_index: usize, ) { + let op_degree = i32::from(op_degree); + let mod_degree = i32::from(mod_degree); let p = self.prime(); let gen_idx = self.fp_idx_to_gen_idx(mod_degree, mod_index); let out_deg = mod_degree + op_degree; @@ -244,7 +249,8 @@ impl Module for FinitelyPresentedModule { } } - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String { + fn basis_element_to_string_multi(&self, degree: MultiDegree<1>, idx: usize) -> String { + let degree = i32::from(degree); let gen_idx = self.fp_idx_to_gen_idx(degree, idx); self.generators.basis_element_to_string(degree, gen_idx) } diff --git a/ext/crates/algebra/src/module/free_module.rs b/ext/crates/algebra/src/module/free_module.rs index 45af27b2c8..4f71719f8b 100644 --- a/ext/crates/algebra/src/module/free_module.rs +++ b/ext/crates/algebra/src/module/free_module.rs @@ -2,10 +2,11 @@ use std::sync::Arc; use fp::vector::{FpSlice, FpSliceMut}; use once::{OnceBiVec, OnceVec}; +use sseq::coordinates::MultiDegree; use crate::{ algebra::MuAlgebra, - module::{Module, ZeroModule}, + module::{Module, ModuleExt, ZeroModule}, }; #[derive(Clone, Debug)] @@ -83,7 +84,8 @@ impl> Module for MuFreeModule { Some(self.min_degree) } - fn compute_basis(&self, max_degree: i32) { + fn compute_basis_multi(&self, max_degree: MultiDegree<1>) { + let max_degree = i32::from(max_degree); let algebra = self.algebra(); self.basis_element_to_opgen.extend(max_degree, |degree| { let new_row = OnceVec::new(); @@ -110,7 +112,8 @@ impl> Module for MuFreeModule { }); } - fn dimension(&self, degree: i32) -> usize { + fn dimension_multi(&self, degree: MultiDegree<1>) -> usize { + let degree = i32::from(degree); if degree < self.min_degree { return 0; } @@ -121,7 +124,8 @@ impl> Module for MuFreeModule { self.basis_element_to_opgen[degree].len() } - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String { + fn basis_element_to_string_multi(&self, degree: MultiDegree<1>, idx: usize) -> String { + let degree = i32::from(degree); let opgen = self.index_to_op_gen(degree, idx); let mut op_str = self .algebra() @@ -137,15 +141,17 @@ impl> Module for MuFreeModule { ) } - fn act_on_basis( + fn act_on_basis_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree<1>, mod_index: usize, ) { + let op_degree = i32::from(op_degree); + let mod_degree = i32::from(mod_degree); let OperationGeneratorPair { operation_degree: module_operation_degree, operation_index: module_operation_index, @@ -177,15 +183,17 @@ impl> Module for MuFreeModule { ); } - fn act( + fn act_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - input_degree: i32, + input_degree: MultiDegree<1>, input: FpSlice, ) { + let op_degree = i32::from(op_degree); + let input_degree = i32::from(input_degree); for GeneratorData { gen_deg, start: [input_start, output_start], diff --git a/ext/crates/algebra/src/module/hom_module.rs b/ext/crates/algebra/src/module/hom_module.rs index e192a92143..8bd74f1436 100644 --- a/ext/crates/algebra/src/module/hom_module.rs +++ b/ext/crates/algebra/src/module/hom_module.rs @@ -3,10 +3,11 @@ use std::sync::Arc; use bivec::BiVec; use fp::vector::FpSliceMut; use once::OnceBiVec; +use sseq::coordinates::MultiDegree; use crate::{ algebra::Field, - module::{FreeModule, Module, block_structure::BlockStructure}, + module::{FreeModule, Module, ModuleExt, block_structure::BlockStructure}, }; /// Given a module N and a free module M, this is the module Hom(M, N) as a module over the ground @@ -76,7 +77,8 @@ impl Module for HomModule { self.source.max_computed_degree() - self.target.max_degree().unwrap() } - fn compute_basis(&self, degree: i32) { + fn compute_basis_multi(&self, degree: MultiDegree<1>) { + let degree = i32::from(degree); self.source .compute_basis(degree + self.target.max_degree().unwrap()); self.block_structures.extend(degree, |d| { @@ -95,25 +97,28 @@ impl Module for HomModule { }); } - fn dimension(&self, degree: i32) -> usize { + fn dimension_multi(&self, degree: MultiDegree<1>) -> usize { + let degree = i32::from(degree); self.block_structures[degree].total_dimension() } - fn act_on_basis( + fn act_on_basis_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - _mod_degree: i32, + _mod_degree: MultiDegree<1>, mod_index: usize, ) { + let op_degree = i32::from(op_degree); assert_eq!(op_degree, 0); assert_eq!(op_index, 0); result.add_basis_element(mod_index, coeff); } - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String { + fn basis_element_to_string_multi(&self, degree: MultiDegree<1>, idx: usize) -> String { + let degree = i32::from(degree); let gen_basis_elt = self.block_structures[degree].index_to_generator_basis_elt(idx); let gen_deg = gen_basis_elt.generator_degree; let gen_idx = gen_basis_elt.generator_index; diff --git a/ext/crates/algebra/src/module/homomorphism/free_module_homomorphism.rs b/ext/crates/algebra/src/module/homomorphism/free_module_homomorphism.rs index cee0d6ef8b..bafec13f3f 100644 --- a/ext/crates/algebra/src/module/homomorphism/free_module_homomorphism.rs +++ b/ext/crates/algebra/src/module/homomorphism/free_module_homomorphism.rs @@ -9,7 +9,7 @@ use once::OnceBiVec; use crate::{ algebra::MuAlgebra, module::{ - Module, MuFreeModule, + Module, ModuleExt, MuFreeModule, free_module::OperationGeneratorPair, homomorphism::{ModuleHomomorphism, ZeroHomomorphism}, }, diff --git a/ext/crates/algebra/src/module/homomorphism/full_module_homomorphism.rs b/ext/crates/algebra/src/module/homomorphism/full_module_homomorphism.rs index 424e45809f..ae236e3e19 100644 --- a/ext/crates/algebra/src/module/homomorphism/full_module_homomorphism.rs +++ b/ext/crates/algebra/src/module/homomorphism/full_module_homomorphism.rs @@ -10,7 +10,7 @@ use once::OnceBiVec; use crate::{ algebra::Algebra, module::{ - Module, + Module, ModuleExt, homomorphism::{IdentityHomomorphism, ModuleHomomorphism, ZeroHomomorphism}, }, }; @@ -145,11 +145,11 @@ where for target_deg in min_degree..=max_degree { let source_deg = target_deg + degree_shift; - // Here we use `Module::dimension(&*m, i)` instead of `m.dimension(i)` because there are + // Here we use `ModuleExt::dimension(&*m, i)` instead of `m.dimension(i)` because there are // multiple `dimension` methods in scope and rust-analyzer gets confused if we're not // explicit enough. - let source_dim = Module::dimension(&*source, source_deg); - let target_dim = Module::dimension(&*target, target_deg); + let source_dim = ModuleExt::dimension(&*source, source_deg); + let target_dim = ModuleExt::dimension(&*target, target_deg); let mut matrix = Matrix::new(p, source_dim, target_dim); f.get_matrix(matrix.as_slice_mut(), source_deg); diff --git a/ext/crates/algebra/src/module/homomorphism/hom_pullback.rs b/ext/crates/algebra/src/module/homomorphism/hom_pullback.rs index 197050fb24..269124d324 100644 --- a/ext/crates/algebra/src/module/homomorphism/hom_pullback.rs +++ b/ext/crates/algebra/src/module/homomorphism/hom_pullback.rs @@ -7,7 +7,7 @@ use fp::{ use once::OnceBiVec; use crate::module::{ - FreeModule, HomModule, Module, + FreeModule, HomModule, Module, ModuleExt, block_structure::GeneratorBasisEltPair, homomorphism::{FreeModuleHomomorphism, ModuleHomomorphism}, }; diff --git a/ext/crates/algebra/src/module/homomorphism/mod.rs b/ext/crates/algebra/src/module/homomorphism/mod.rs index 540b34ac4b..4fef229492 100644 --- a/ext/crates/algebra/src/module/homomorphism/mod.rs +++ b/ext/crates/algebra/src/module/homomorphism/mod.rs @@ -6,7 +6,7 @@ use fp::{ vector::{FpSlice, FpSliceMut}, }; -use crate::module::Module; +use crate::module::{Module, ModuleExt}; mod free_module_homomorphism; mod full_module_homomorphism; diff --git a/ext/crates/algebra/src/module/homomorphism/quotient_homomorphism.rs b/ext/crates/algebra/src/module/homomorphism/quotient_homomorphism.rs index e815b17304..233aa37207 100644 --- a/ext/crates/algebra/src/module/homomorphism/quotient_homomorphism.rs +++ b/ext/crates/algebra/src/module/homomorphism/quotient_homomorphism.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use fp::vector::{FpSliceMut, FpVector}; -use crate::module::{Module, QuotientModule, homomorphism::ModuleHomomorphism}; +use crate::module::{ModuleExt, QuotientModule, homomorphism::ModuleHomomorphism}; pub struct QuotientHomomorphism { f: Arc, diff --git a/ext/crates/algebra/src/module/mod.rs b/ext/crates/algebra/src/module/mod.rs index 2bd760fdc6..b8fa9a301d 100644 --- a/ext/crates/algebra/src/module/mod.rs +++ b/ext/crates/algebra/src/module/mod.rs @@ -21,7 +21,7 @@ pub use free_module::{ FreeModule, GeneratorData, MuFreeModule, OperationGeneratorPair, UnstableFreeModule, }; pub use hom_module::HomModule; -pub use module_trait::{ActError, Module, ModuleFailedRelationError}; +pub use module_trait::{ActError, Module, ModuleExt, ModuleFailedRelationError}; pub use quotient_module::QuotientModule; pub use rpn::RealProjectiveSpace; pub use steenrod_module::SteenrodModule; diff --git a/ext/crates/algebra/src/module/module_trait.rs b/ext/crates/algebra/src/module/module_trait.rs index 9ef4ee6400..28325b875c 100644 --- a/ext/crates/algebra/src/module/module_trait.rs +++ b/ext/crates/algebra/src/module/module_trait.rs @@ -6,6 +6,7 @@ use fp::{ vector::{FpSlice, FpSliceMut}, }; use itertools::Itertools; +use sseq::coordinates::MultiDegree; use crate::algebra::Algebra; @@ -18,7 +19,7 @@ use crate::algebra::Algebra; /// - [`Module::max_computed_degree`] gives the maximum degree for which the module is fully /// defined. It is guaranteed that the module will never change up to this degree in the future. /// -/// - [`Module::compute_basis`] extends the internal data to support querying data up to (and +/// - [`Module::compute_basis_multi`] extends the internal data to support querying data up to (and /// including) a given degree. In general, we can run this beyond the max computed degree. /// /// A useful example to keep in mind is a [`FreeModule`](crate::module::FreeModule), where we have @@ -26,8 +27,8 @@ use crate::algebra::Algebra; /// `compute_basis` computes data such as the offset of existing generators in potentially higher /// degrees. #[auto_impl(Arc, Box)] -pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { - type Algebra: Algebra; +pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { + type Algebra: Algebra; /// The algebra the module is over. fn algebra(&self) -> Arc; @@ -42,37 +43,37 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { /// /// See [`Module`] documentation for more details. #[allow(unused_variables)] - fn compute_basis(&self, degree: i32) {} + fn compute_basis_multi(&self, degree: MultiDegree) {} /// The maximum `t` for which the module is fully defined at `t`. See [`Module`] documentation /// for more details. fn max_computed_degree(&self) -> i32; /// The dimension of a module at the given degree - fn dimension(&self, degree: i32) -> usize; - fn act_on_basis( + fn dimension_multi(&self, degree: MultiDegree) -> usize; + fn act_on_basis_multi( &self, result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree, mod_index: usize, ); - /// Non-panicking variant of [`Module::act_on_basis`]. Validates the operation and + /// Non-panicking variant of [`Module::act_on_basis_multi`]. Validates the operation and /// module degrees/indices and returns an [`ActError`] describing the problem instead of - /// panicking. On success it delegates to [`Module::act_on_basis`] and returns `Ok(())`. - fn try_act_on_basis( + /// panicking. On success it delegates to [`Module::act_on_basis_multi`] and returns `Ok(())`. + fn try_act_on_basis_multi( &self, result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree, mod_index: usize, ) -> Result<(), ActError> { - if op_degree < 0 { + if op_degree.t() < 0 { return Err(ActError::IndexOutOfRange(format!( "op_degree {op_degree} is negative" ))); @@ -86,36 +87,36 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { ))); } let min_degree = self.min_degree(); - if mod_degree < min_degree { + if mod_degree.t() < min_degree { return Err(ActError::IndexOutOfRange(format!( "mod_degree {mod_degree} is below the module's min degree {min_degree}" ))); } - self.compute_basis(mod_degree); - let mod_dim = self.dimension(mod_degree); + self.compute_basis_multi(mod_degree); + let mod_dim = self.dimension_multi(mod_degree); if mod_index >= mod_dim { return Err(ActError::IndexOutOfRange(format!( "mod_index {mod_index} out of range for module dimension {mod_dim} in degree \ {mod_degree}" ))); } - self.act_on_basis(result, coeff, op_degree, op_index, mod_degree, mod_index); + self.act_on_basis_multi(result, coeff, op_degree, op_index, mod_degree, mod_index); Ok(()) } - /// Non-panicking variant of [`Module::act`]. Validates the operation degree/index and + /// Non-panicking variant of [`Module::act_multi`]. Validates the operation degree/index and /// the input degree/length and returns an [`ActError`] describing the problem instead of - /// panicking. On success it delegates to [`Module::act`] and returns `Ok(())`. - fn try_act( + /// panicking. On success it delegates to [`Module::act_multi`] and returns `Ok(())`. + fn try_act_multi( &self, result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree, op_index: usize, - input_degree: i32, + input_degree: MultiDegree, input: FpSlice, ) -> Result<(), ActError> { - if op_degree < 0 { + if op_degree.t() < 0 { return Err(ActError::IndexOutOfRange(format!( "op_degree {op_degree} is negative" ))); @@ -129,29 +130,39 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { ))); } let min_degree = self.min_degree(); - if input_degree < min_degree { + if input_degree.t() < min_degree { return Err(ActError::IndexOutOfRange(format!( "input_degree {input_degree} is below the module's min degree {min_degree}" ))); } - self.compute_basis(input_degree); - let input_dim = self.dimension(input_degree); + self.compute_basis_multi(input_degree); + let input_dim = self.dimension_multi(input_degree); if input.len() > input_dim { return Err(ActError::InvalidInput(format!( "input length {} exceeds module dimension {input_dim} in degree {input_degree}", input.len() ))); } - self.act(result, coeff, op_degree, op_index, input_degree, input); + self.act_multi(result, coeff, op_degree, op_index, input_degree, input); Ok(()) } /// The name of a basis element. This is useful for debugging and printing results. - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String; + fn basis_element_to_string_multi(&self, degree: MultiDegree, idx: usize) -> String; /// Whether this is the unit module. + /// + /// The default answers this from `min_degree`/`max_degree`, which are the (single) `i32` + /// filtration direction. That cannot characterize the unit when `N > 1` (distinct multidegrees + /// can share a `t`, so an extra component is invisible here), so the default conservatively + /// returns `false` for multigraded modules; such modules should override this if they need it. fn is_unit(&self) -> bool { - self.min_degree() == 0 && self.max_degree() == Some(0) && self.dimension(0) == 1 + if N > 1 { + return false; + } + self.min_degree() == 0 + && self.max_degree() == Some(0) + && self.dimension_multi(MultiDegree::zero()) == 1 } /// The prime the module is over, which should be equal to the prime of the algebra. @@ -159,7 +170,7 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { self.algebra().prime() } - /// `max_degree` is the a degree such that if t > `max_degree`, then `self.dimension(t) = 0`. + /// `max_degree` is the a degree such that if t > `max_degree`, then `self.dimension_multi(t) = 0`. fn max_degree(&self) -> Option { None } @@ -170,13 +181,16 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { self.max_degree() } - fn total_dimension(&self) -> usize { + fn total_dimension(&self) -> usize + where + MultiDegree: From, + { let max_degree = self .max_degree() .expect("total_dimension requires module to be bounded"); (self.min_degree()..=max_degree) - .map(|i| self.dimension(i)) + .map(|i| self.dimension_multi(MultiDegree::from(i))) .sum() } @@ -186,19 +200,19 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { /// This flexibility is useful when resolving to a stem. The point is that we have elements in /// degree `t` that are guaranteed to not contain generators of degree `t`, and we don't know /// what generators will be added in degree `t` yet. - fn act( + fn act_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree, op_index: usize, - input_degree: i32, + input_degree: MultiDegree, input: FpSlice, ) { - assert!(input.len() <= self.dimension(input_degree)); + assert!(input.len() <= self.dimension_multi(input_degree)); let p = self.prime(); for (i, v) in input.iter_nonzero() { - self.act_on_basis( + self.act_on_basis_multi( result.copy(), (coeff * v) % p, op_degree, @@ -209,20 +223,20 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { } } - fn act_by_element( + fn act_by_element_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree, op: FpSlice, - input_degree: i32, + input_degree: MultiDegree, input: FpSlice, ) { - assert_eq!(input.len(), self.dimension(input_degree)); + assert_eq!(input.len(), self.dimension_multi(input_degree)); assert_eq!(op.len(), self.algebra().dimension(op_degree)); let p = self.prime(); for (i, v) in op.iter_nonzero() { - self.act( + self.act_multi( result.copy(), (coeff * v) % p, op_degree, @@ -233,19 +247,19 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { } } - fn act_by_element_on_basis( + fn act_by_element_on_basis_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree, op: FpSlice, - input_degree: i32, + input_degree: MultiDegree, input_index: usize, ) { assert_eq!(op.len(), self.algebra().dimension(op_degree)); let p = self.prime(); for (i, v) in op.iter_nonzero() { - self.act_on_basis( + self.act_on_basis_multi( result.copy(), (coeff * v) % p, op_degree, @@ -257,8 +271,8 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { } /// Gives the name of an element. The default implementation is derived from - /// [`Module::basis_element_to_string`] in the obvious way. - fn element_to_string(&self, degree: i32, element: FpSlice) -> String { + /// [`Module::basis_element_to_string_multi`] in the obvious way. + fn element_to_string_multi(&self, degree: MultiDegree, element: FpSlice) -> String { let result = element .iter_nonzero() .map(|(idx, value)| { @@ -267,7 +281,7 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { } else { format!("{value} ") }; - let basis_elt = self.basis_element_to_string(degree, idx); + let basis_elt = self.basis_element_to_string_multi(degree, idx); format!("{coeff}{basis_elt}") }) .join(" + "); @@ -279,6 +293,159 @@ pub trait Module: std::fmt::Display + std::any::Any + Send + Sync { } } +/// Ergonomic, singly-graded-friendly wrappers over [`Module`]. +/// +/// The object-safe [`Module`] trait takes concrete [`MultiDegree`] degrees, so it stays usable +/// as `dyn Module`. This blanket extension exposes the same operations under their canonical names +/// (`dimension`, `act_on_basis`, …) taking `impl Into>`, so callers keep passing +/// bare `i32`s in the singly-graded (`N = 1`) world. It is implemented for every module, including +/// `dyn Module` (via `?Sized`), so the ergonomic names are always available. +pub trait ModuleExt: Module { + /// See [`Module::compute_basis_multi`]. + fn compute_basis(&self, degree: impl Into>) { + self.compute_basis_multi(degree.into()) + } + + /// See [`Module::dimension_multi`]. + fn dimension(&self, degree: impl Into>) -> usize { + self.dimension_multi(degree.into()) + } + + /// See [`Module::act_on_basis_multi`]. + #[allow(clippy::too_many_arguments)] + fn act_on_basis( + &self, + result: FpSliceMut, + coeff: u32, + op_degree: impl Into>, + op_index: usize, + mod_degree: impl Into>, + mod_index: usize, + ) { + self.act_on_basis_multi( + result, + coeff, + op_degree.into(), + op_index, + mod_degree.into(), + mod_index, + ) + } + + /// See [`Module::try_act_on_basis_multi`]. + #[allow(clippy::too_many_arguments)] + fn try_act_on_basis( + &self, + result: FpSliceMut, + coeff: u32, + op_degree: impl Into>, + op_index: usize, + mod_degree: impl Into>, + mod_index: usize, + ) -> Result<(), ActError> { + self.try_act_on_basis_multi( + result, + coeff, + op_degree.into(), + op_index, + mod_degree.into(), + mod_index, + ) + } + + /// See [`Module::try_act_multi`]. + fn try_act( + &self, + result: FpSliceMut, + coeff: u32, + op_degree: impl Into>, + op_index: usize, + input_degree: impl Into>, + input: FpSlice, + ) -> Result<(), ActError> { + self.try_act_multi( + result, + coeff, + op_degree.into(), + op_index, + input_degree.into(), + input, + ) + } + + /// See [`Module::basis_element_to_string_multi`]. + fn basis_element_to_string(&self, degree: impl Into>, idx: usize) -> String { + self.basis_element_to_string_multi(degree.into(), idx) + } + + /// See [`Module::act_multi`]. + fn act( + &self, + result: FpSliceMut, + coeff: u32, + op_degree: impl Into>, + op_index: usize, + input_degree: impl Into>, + input: FpSlice, + ) { + self.act_multi( + result, + coeff, + op_degree.into(), + op_index, + input_degree.into(), + input, + ) + } + + /// See [`Module::act_by_element_multi`]. + fn act_by_element( + &self, + result: FpSliceMut, + coeff: u32, + op_degree: impl Into>, + op: FpSlice, + input_degree: impl Into>, + input: FpSlice, + ) { + self.act_by_element_multi( + result, + coeff, + op_degree.into(), + op, + input_degree.into(), + input, + ) + } + + /// See [`Module::act_by_element_on_basis_multi`]. + fn act_by_element_on_basis( + &self, + result: FpSliceMut, + coeff: u32, + op_degree: impl Into>, + op: FpSlice, + input_degree: impl Into>, + input_index: usize, + ) { + self.act_by_element_on_basis_multi( + result, + coeff, + op_degree.into(), + op, + input_degree.into(), + input_index, + ) + } + + /// See [`Module::element_to_string_multi`]. + fn element_to_string(&self, degree: impl Into>, element: FpSlice) -> String { + self.element_to_string_multi(degree.into(), element) + } +} + +impl + ?Sized> ModuleExt for M {} + #[derive(Debug)] pub struct ModuleFailedRelationError { pub relation: String, @@ -297,7 +464,7 @@ impl std::fmt::Display for ModuleFailedRelationError { impl std::error::Error for ModuleFailedRelationError {} -/// Error returned by [`Module::try_act`] and [`Module::try_act_on_basis`]. +/// Error returned by [`Module::try_act_multi`] and [`Module::try_act_on_basis_multi`]. /// /// The variants separate the distinct failure categories so callers (e.g. the /// Python bindings) can map them to different error types. diff --git a/ext/crates/algebra/src/module/quotient_module.rs b/ext/crates/algebra/src/module/quotient_module.rs index ba983bce69..d53d38f182 100644 --- a/ext/crates/algebra/src/module/quotient_module.rs +++ b/ext/crates/algebra/src/module/quotient_module.rs @@ -6,8 +6,9 @@ use fp::{ matrix::Subspace, vector::{FpSlice, FpSliceMut, FpVector}, }; +use sseq::coordinates::MultiDegree; -use crate::module::{Module, ZeroModule}; +use crate::module::{Module, ModuleExt, ZeroModule}; /// A quotient of a module truncated below a fix degree. pub struct QuotientModule { @@ -173,7 +174,8 @@ impl Module for QuotientModule { self.module.max_computed_degree() } - fn dimension(&self, degree: i32) -> usize { + fn dimension_multi(&self, degree: MultiDegree<1>) -> usize { + let degree = i32::from(degree); if degree > self.truncation { 0 } else { @@ -181,15 +183,17 @@ impl Module for QuotientModule { } } - fn act_on_basis( + fn act_on_basis_multi( &self, result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree<1>, mod_index: usize, ) { + let op_degree = i32::from(op_degree); + let mod_degree = i32::from(mod_degree); let target_deg = op_degree + mod_degree; if target_deg > self.truncation { return; @@ -208,7 +212,8 @@ impl Module for QuotientModule { self.old_basis_to_new(target_deg, result, result_.as_slice()); } - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String { + fn basis_element_to_string_multi(&self, degree: MultiDegree<1>, idx: usize) -> String { + let degree = i32::from(degree); self.module .basis_element_to_string(degree, self.basis_list[degree][idx]) } diff --git a/ext/crates/algebra/src/module/rpn.rs b/ext/crates/algebra/src/module/rpn.rs index 6d42a1f9f8..5b5b0d0a98 100644 --- a/ext/crates/algebra/src/module/rpn.rs +++ b/ext/crates/algebra/src/module/rpn.rs @@ -6,6 +6,7 @@ use fp::{ }; use serde::Deserialize; use serde_json::Value; +use sseq::coordinates::MultiDegree; use crate::{ algebra::{ @@ -13,7 +14,7 @@ use crate::{ adem_algebra::AdemBasisElement, milnor_algebra::{MilnorBasisElement, PPartEntry}, }, - module::{Module, ZeroModule}, + module::{Module, ModuleExt, ZeroModule}, }; /// This is $\mathbb{RP}_{\mathrm{min}}^{\mathrm{max}}$. The cohomology is the subquotient of @@ -72,7 +73,8 @@ where i32::MAX } - fn dimension(&self, degree: i32) -> usize { + fn dimension_multi(&self, degree: MultiDegree<1>) -> usize { + let degree = i32::from(degree); if degree < self.min { return 0; } @@ -94,20 +96,23 @@ where 1 } - fn basis_element_to_string(&self, degree: i32, _idx: usize) -> String { + fn basis_element_to_string_multi(&self, degree: MultiDegree<1>, _idx: usize) -> String { + let degree = i32::from(degree); // It is an error to call the function if self.dimension(degree) == 0 format!("x^{{{degree}}}") } - fn act_on_basis( + fn act_on_basis_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree<1>, mod_index: usize, ) { + let op_degree = i32::from(op_degree); + let mod_degree = i32::from(mod_degree); assert!(op_index < self.algebra().dimension(op_degree)); assert!(mod_index < self.dimension(mod_degree)); diff --git a/ext/crates/algebra/src/module/suspension_module.rs b/ext/crates/algebra/src/module/suspension_module.rs index 8b92ac1f5e..7036cfb49d 100644 --- a/ext/crates/algebra/src/module/suspension_module.rs +++ b/ext/crates/algebra/src/module/suspension_module.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use crate::module::{Module, ZeroModule}; +use sseq::coordinates::MultiDegree; + +use crate::module::{Module, ModuleExt, ZeroModule}; pub struct SuspensionModule { inner: Arc, @@ -27,7 +29,8 @@ impl std::fmt::Display for SuspensionModule { impl Module for SuspensionModule { type Algebra = M::Algebra; - fn compute_basis(&self, degree: i32) { + fn compute_basis_multi(&self, degree: MultiDegree<1>) { + let degree = i32::from(degree); self.inner.compute_basis(degree - self.shift); } @@ -51,15 +54,17 @@ impl Module for SuspensionModule { self.inner.total_dimension() } - fn act( + fn act_multi( &self, result: fp::vector::FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - input_degree: i32, + input_degree: MultiDegree<1>, input: fp::vector::FpSlice, ) { + let op_degree = i32::from(op_degree); + let input_degree = i32::from(input_degree); self.inner.act( result, coeff, @@ -70,15 +75,17 @@ impl Module for SuspensionModule { ); } - fn act_by_element( + fn act_by_element_multi( &self, result: fp::vector::FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op: fp::vector::FpSlice, - input_degree: i32, + input_degree: MultiDegree<1>, input: fp::vector::FpSlice, ) { + let op_degree = i32::from(op_degree); + let input_degree = i32::from(input_degree); self.inner.act_by_element( result, coeff, @@ -89,15 +96,17 @@ impl Module for SuspensionModule { ); } - fn act_by_element_on_basis( + fn act_by_element_on_basis_multi( &self, result: fp::vector::FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op: fp::vector::FpSlice, - input_degree: i32, + input_degree: MultiDegree<1>, input_index: usize, ) { + let op_degree = i32::from(op_degree); + let input_degree = i32::from(input_degree); self.inner.act_by_element_on_basis( result, coeff, @@ -108,7 +117,12 @@ impl Module for SuspensionModule { ); } - fn element_to_string(&self, degree: i32, element: fp::vector::FpSlice) -> String { + fn element_to_string_multi( + &self, + degree: MultiDegree<1>, + element: fp::vector::FpSlice, + ) -> String { + let degree = i32::from(degree); self.inner.element_to_string(degree - self.shift, element) } @@ -124,19 +138,22 @@ impl Module for SuspensionModule { self.inner.max_computed_degree() + self.shift } - fn dimension(&self, degree: i32) -> usize { + fn dimension_multi(&self, degree: MultiDegree<1>) -> usize { + let degree = i32::from(degree); self.inner.dimension(degree - self.shift) } - fn act_on_basis( + fn act_on_basis_multi( &self, result: fp::vector::FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree<1>, mod_index: usize, ) { + let op_degree = i32::from(op_degree); + let mod_degree = i32::from(mod_degree); self.inner.act_on_basis( result, coeff, @@ -147,7 +164,8 @@ impl Module for SuspensionModule { ); } - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String { + fn basis_element_to_string_multi(&self, degree: MultiDegree<1>, idx: usize) -> String { + let degree = i32::from(degree); self.inner.basis_element_to_string(degree - self.shift, idx) } } diff --git a/ext/crates/algebra/src/module/tensor_module.rs b/ext/crates/algebra/src/module/tensor_module.rs index 64f4def256..a7b5e283a5 100644 --- a/ext/crates/algebra/src/module/tensor_module.rs +++ b/ext/crates/algebra/src/module/tensor_module.rs @@ -6,10 +6,11 @@ use fp::{ vector::{FpSlice, FpSliceMut, FpVector}, }; use once::OnceBiVec; +use sseq::coordinates::MultiDegree; use crate::{ algebra::{Algebra, Bialgebra}, - module::{Module, ZeroModule, block_structure::BlockStructure}, + module::{Module, ModuleExt, ZeroModule, block_structure::BlockStructure}, }; // This really only makes sense when the algebra is a bialgebra, but associated type bounds are @@ -76,14 +77,14 @@ where for left_deg in self.left.min_degree()..=(mod_degree - self.right.min_degree()) { let right_deg = mod_degree - left_deg; - // Here we use `Module::dimension(&*m, i)` instead of `m.dimension(i)` because there are + // Here we use `ModuleExt::dimension(&*m, i)` instead of `m.dimension(i)` because there are // multiple `dimension` methods in scope and rust-analyzer gets confused if we're not // explicit enough. - let left_source_dim = Module::dimension(&*self.left, left_deg); - let right_source_dim = Module::dimension(&*self.right, right_deg); + let left_source_dim = ModuleExt::dimension(&*self.left, left_deg); + let right_source_dim = ModuleExt::dimension(&*self.right, right_deg); - let left_target_dim = Module::dimension(&*self.left, left_deg + op_deg_l); - let right_target_dim = Module::dimension(&*self.right, right_deg + op_deg_r); + let left_target_dim = ModuleExt::dimension(&*self.left, left_deg + op_deg_l); + let right_target_dim = ModuleExt::dimension(&*self.right, right_deg + op_deg_r); if left_target_dim == 0 || right_target_dim == 0 @@ -165,19 +166,21 @@ where self.block_structures.len() } - fn compute_basis(&self, degree: i32) { + fn compute_basis_multi(&self, degree: MultiDegree<1>) { + let degree = i32::from(degree); self.left.compute_basis(degree - self.right.min_degree()); self.right.compute_basis(degree - self.left.min_degree()); self.block_structures.extend(degree, |i| { let mut block_sizes = BiVec::with_capacity(self.left.min_degree(), i - self.right.min_degree() + 1); for j in self.left.min_degree()..=i - self.right.min_degree() { - // Here we use `Module::dimension(&*m, i)` instead of `m.dimension(i)` because there are + // Here we use `ModuleExt::dimension(&*m, i)` instead of `m.dimension(i)` because there are // multiple `dimension` methods in scope and rust-analyzer gets confused if we're not // explicit enough. - let mut block_sizes_entry = Vec::with_capacity(Module::dimension(&*self.left, j)); - for _ in 0..Module::dimension(&*self.left, j) { - block_sizes_entry.push(Module::dimension(&*self.right, i - j)) + let mut block_sizes_entry = + Vec::with_capacity(ModuleExt::dimension(&*self.left, j)); + for _ in 0..ModuleExt::dimension(&*self.left, j) { + block_sizes_entry.push(ModuleExt::dimension(&*self.right, i - j)) } block_sizes.push(block_sizes_entry); } @@ -186,19 +189,22 @@ where }); } - fn dimension(&self, degree: i32) -> usize { + fn dimension_multi(&self, degree: MultiDegree<1>) -> usize { + let degree = i32::from(degree); self.block_structures[degree].total_dimension() } - fn act_on_basis( + fn act_on_basis_multi( &self, result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree<1>, mod_index: usize, ) { + let op_degree = i32::from(op_degree); + let mod_degree = i32::from(mod_degree); let mut working_element = FpVector::new(self.prime(), self.dimension(mod_degree)); working_element.set_entry(mod_index, 1); @@ -212,15 +218,17 @@ where ); } - fn act( + fn act_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree<1>, input: FpSlice, ) { + let op_degree = i32::from(op_degree); + let mod_degree = i32::from(mod_degree); if op_degree == 0 { result.add(input, coeff); return; @@ -276,15 +284,16 @@ where } } - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String { + fn basis_element_to_string_multi(&self, degree: MultiDegree<1>, idx: usize) -> String { + let degree = i32::from(degree); let left_degree = self.seek_module_num(degree, idx); let right_degree = degree - left_degree; let inner_index = idx - self.offset(degree, left_degree); - // Here we use `Module::dimension(&*m, i)` instead of `m.dimension(i)` because there are + // Here we use `ModuleExt::dimension(&*m, i)` instead of `m.dimension(i)` because there are // multiple `dimension` methods in scope and rust-analyzer gets confused if we're not // explicit enough. - let right_dim = Module::dimension(&*self.right, right_degree); + let right_dim = ModuleExt::dimension(&*self.right, right_degree); let left_index = inner_index / right_dim; let right_index = inner_index % right_dim; diff --git a/ext/crates/sseq/src/coordinates/degree.rs b/ext/crates/sseq/src/coordinates/degree.rs index d9fbe51c0c..9d71921e61 100644 --- a/ext/crates/sseq/src/coordinates/degree.rs +++ b/ext/crates/sseq/src/coordinates/degree.rs @@ -66,6 +66,21 @@ impl From<[i32; N]> for MultiDegree { } } +/// A single-graded degree is just an `i32`. This lets every `i32` degree in the singly-graded +/// (`N == 1`) world coerce into a `MultiDegree<1>`, so callers can keep passing bare `i32`s to the +/// multigraded trait methods (which take `impl Into>`). +impl From for MultiDegree<1> { + fn from(t: i32) -> Self { + Self { coords: [t] } + } +} + +impl From> for i32 { + fn from(d: MultiDegree<1>) -> Self { + d.coords[0] + } +} + impl From> for [i32; N] { fn from(d: MultiDegree) -> [i32; N] { d.coords diff --git a/ext/examples/bruner.rs b/ext/examples/bruner.rs index 71c5c0e22a..ba6887da39 100644 --- a/ext/examples/bruner.rs +++ b/ext/examples/bruner.rs @@ -26,7 +26,7 @@ use std::{ use algebra::{ Algebra, MilnorAlgebra, milnor_algebra::MilnorBasisElement, - module::{FreeModule as FM, Module, homomorphism::FreeModuleHomomorphism as FMH}, + module::{FreeModule as FM, ModuleExt, homomorphism::FreeModuleHomomorphism as FMH}, }; use anyhow::{Context, Error, Result}; use ext::{ diff --git a/ext/examples/ext_m_n.rs b/ext/examples/ext_m_n.rs index 0796354f3d..a142796f8e 100644 --- a/ext/examples/ext_m_n.rs +++ b/ext/examples/ext_m_n.rs @@ -52,7 +52,7 @@ mod hom_cochain_complex { use std::sync::Arc; use algebra::module::{ - HomModule, Module, + HomModule, Module, ModuleExt, homomorphism::{HomPullback, ModuleHomomorphism}, }; use ext::chain_complex::FreeChainComplex; diff --git a/ext/examples/lift_hom.rs b/ext/examples/lift_hom.rs index 3958a6069c..aa79b4272c 100644 --- a/ext/examples/lift_hom.rs +++ b/ext/examples/lift_hom.rs @@ -41,7 +41,7 @@ use std::{path::PathBuf, sync::Arc}; -use algebra::module::Module; +use algebra::module::{Module, ModuleExt}; use anyhow::{Context, anyhow}; use ext::{ chain_complex::{AugmentedChainComplex, ChainComplex, FreeChainComplex}, diff --git a/ext/examples/resolution_size.rs b/ext/examples/resolution_size.rs index 50df7eb63a..d2be52f422 100644 --- a/ext/examples/resolution_size.rs +++ b/ext/examples/resolution_size.rs @@ -1,4 +1,4 @@ -use algebra::module::Module; +use algebra::module::{Module, ModuleExt}; use ext::{chain_complex::ChainComplex, utils::query_module}; fn main() -> anyhow::Result<()> { diff --git a/ext/examples/sq0.rs b/ext/examples/sq0.rs index dc671c159b..9c0bb02337 100644 --- a/ext/examples/sq0.rs +++ b/ext/examples/sq0.rs @@ -144,11 +144,12 @@ mod double { pub mod double_module { use std::sync::Arc; - use algebra::module::{Module, homomorphism::ModuleHomomorphism}; + use algebra::module::{Module, ModuleExt, homomorphism::ModuleHomomorphism}; use fp::{ matrix::{Matrix, MatrixSliceMut, QuasiInverse, Subspace}, vector::{FpSlice, FpSliceMut}, }; + use sseq::coordinates::MultiDegree; use super::DoubleAlgebra; @@ -189,7 +190,8 @@ mod double { self.inner.max_computed_degree() * 2 } - fn dimension(&self, degree: i32) -> usize { + fn dimension_multi(&self, degree: MultiDegree<1>) -> usize { + let degree = i32::from(degree); if degree % 2 == 0 { self.inner.dimension(degree / 2) } else { @@ -197,15 +199,17 @@ mod double { } } - fn act_on_basis( + fn act_on_basis_multi( &self, result: fp::vector::FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree<1>, mod_index: usize, ) { + let op_degree = i32::from(op_degree); + let mod_degree = i32::from(mod_degree); if op_degree % 2 == 1 { return; } @@ -221,7 +225,8 @@ mod double { } } - fn basis_element_to_string(&self, degree: i32, idx: usize) -> String { + fn basis_element_to_string_multi(&self, degree: MultiDegree<1>, idx: usize) -> String { + let degree = i32::from(degree); self.inner.basis_element_to_string(degree / 2, idx) } @@ -251,15 +256,17 @@ mod double { /// This flexibility is useful when resolving to a stem. The point is that we have elements in /// degree `t` that are guaranteed to not contain generators of degree `t`, and we don't know /// what generators will be added in degree `t` yet. - fn act( + fn act_multi( &self, result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - input_degree: i32, + input_degree: MultiDegree<1>, input: FpSlice, ) { + let op_degree = i32::from(op_degree); + let input_degree = i32::from(input_degree); if op_degree % 2 == 1 { return; } @@ -277,7 +284,8 @@ mod double { /// Gives the name of an element. The default implementation is derived from /// [`Module::basis_element_to_string`] in the obvious way. - fn element_to_string(&self, degree: i32, element: FpSlice) -> String { + fn element_to_string_multi(&self, degree: MultiDegree<1>, element: FpSlice) -> String { + let degree = i32::from(degree); self.inner.element_to_string(degree, element) } } diff --git a/ext/examples/steenrod.rs b/ext/examples/steenrod.rs index e94b301d6e..d4a18c4e21 100644 --- a/ext/examples/steenrod.rs +++ b/ext/examples/steenrod.rs @@ -4,7 +4,7 @@ use std::{ }; use algebra::module::{ - Module, + Module, ModuleExt, homomorphism::{FreeModuleHomomorphism, ModuleHomomorphism}, }; use ext::{ @@ -190,12 +190,13 @@ mod sum_module { use std::sync::Arc; use algebra::module::{ - Module, ZeroModule, + Module, ModuleExt, ZeroModule, block_structure::{BlockStructure, GeneratorBasisEltPair}, }; use bivec::BiVec; use fp::vector::FpSliceMut; use once::OnceBiVec; + use sseq::coordinates::MultiDegree; pub struct SumModule { // We need these because modules might be empty @@ -253,7 +254,8 @@ mod sum_module { self.min_degree } - fn compute_basis(&self, degree: i32) { + fn compute_basis_multi(&self, degree: MultiDegree<1>) { + let degree = i32::from(degree); for module in &self.modules { module.compute_basis(degree); } @@ -269,22 +271,25 @@ mod sum_module { self.block_structures.len() } - fn dimension(&self, degree: i32) -> usize { + fn dimension_multi(&self, degree: MultiDegree<1>) -> usize { + let degree = i32::from(degree); self.block_structures .get(degree) .map(BlockStructure::total_dimension) .unwrap_or(0) } - fn act_on_basis( + fn act_on_basis_multi( &self, mut result: FpSliceMut, coeff: u32, - op_degree: i32, + op_degree: MultiDegree<1>, op_index: usize, - mod_degree: i32, + mod_degree: MultiDegree<1>, mod_index: usize, ) { + let op_degree = i32::from(op_degree); + let mod_degree = i32::from(mod_degree); let target_degree = mod_degree + op_degree; let GeneratorBasisEltPair { generator_index: module_num, @@ -305,7 +310,8 @@ mod sum_module { ); } - fn basis_element_to_string(&self, degree: i32, index: usize) -> String { + fn basis_element_to_string_multi(&self, degree: MultiDegree<1>, index: usize) -> String { + let degree = i32::from(degree); let GeneratorBasisEltPair { generator_index: module_num, basis_index, @@ -379,7 +385,7 @@ mod tensor_product_chain_complex { use algebra::{ Algebra, Bialgebra, - module::{Module, TensorModule, ZeroModule, homomorphism::ModuleHomomorphism}, + module::{ModuleExt, TensorModule, ZeroModule, homomorphism::ModuleHomomorphism}, }; use ext::chain_complex::ChainComplex; use fp::{ diff --git a/ext/src/chain_complex/chain_homotopy.rs b/ext/src/chain_complex/chain_homotopy.rs index c0ec0fd0d2..533f9ba8f5 100644 --- a/ext/src/chain_complex/chain_homotopy.rs +++ b/ext/src/chain_complex/chain_homotopy.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, Mutex}; use algebra::module::{ - Module, + Module, ModuleExt, homomorphism::{FreeModuleHomomorphism, ModuleHomomorphism}, }; use fp::{prime::ValidPrime, vector::FpVector}; @@ -294,7 +294,7 @@ pub(crate) mod secondary { use std::sync::Arc; use algebra::{ - module::{Module, homomorphism::ModuleHomomorphism}, + module::{ModuleExt, homomorphism::ModuleHomomorphism}, pair_algebra::PairAlgebra, }; use dashmap::DashMap; diff --git a/ext/src/chain_complex/finite_chain_complex.rs b/ext/src/chain_complex/finite_chain_complex.rs index e5a068ffd0..c3b7a2e6ef 100644 --- a/ext/src/chain_complex/finite_chain_complex.rs +++ b/ext/src/chain_complex/finite_chain_complex.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use algebra::module::{ - Module, ZeroModule, + Module, ModuleExt, ZeroModule, homomorphism::{FullModuleHomomorphism, ModuleHomomorphism, ZeroHomomorphism}, }; use sseq::coordinates::Bidegree; diff --git a/ext/src/chain_complex/mod.rs b/ext/src/chain_complex/mod.rs index cf9b275801..996b94858e 100644 --- a/ext/src/chain_complex/mod.rs +++ b/ext/src/chain_complex/mod.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use algebra::{ Algebra, MuAlgebra, module::{ - Module, MuFreeModule, + Module, ModuleExt, MuFreeModule, homomorphism::{ModuleHomomorphism, MuFreeModuleHomomorphism}, }, }; diff --git a/ext/src/nassau.rs b/ext/src/nassau.rs index 8d1919136a..4e78afb631 100644 --- a/ext/src/nassau.rs +++ b/ext/src/nassau.rs @@ -22,7 +22,7 @@ use algebra::{ Algebra, combinatorics, milnor_algebra::{MilnorAlgebra, PPartEntry}, module::{ - FreeModule, GeneratorData, Module, ZeroModule, + FreeModule, GeneratorData, Module, ModuleExt, ZeroModule, homomorphism::{FreeModuleHomomorphism, FullModuleHomomorphism, ModuleHomomorphism}, }, }; diff --git a/ext/src/resolution.rs b/ext/src/resolution.rs index d37edd20ed..ac459a5461 100644 --- a/ext/src/resolution.rs +++ b/ext/src/resolution.rs @@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex, mpsc}; use algebra::{ Algebra, MuAlgebra, module::{ - Module, MuFreeModule, + ModuleExt, MuFreeModule, homomorphism::{ModuleHomomorphism, MuFreeModuleHomomorphism}, }, }; @@ -985,7 +985,10 @@ where pub(crate) mod secondary { use std::sync::Arc; - use algebra::{module::Module, pair_algebra::PairAlgebra}; + use algebra::{ + module::{Module, ModuleExt}, + pair_algebra::PairAlgebra, + }; use dashmap::DashMap; use fp::vector::FpVector; use once::OnceBiVec; diff --git a/ext/src/resolution_homomorphism.rs b/ext/src/resolution_homomorphism.rs index 271c7318d3..48c6f2288a 100644 --- a/ext/src/resolution_homomorphism.rs +++ b/ext/src/resolution_homomorphism.rs @@ -5,7 +5,7 @@ use std::{ops::Range, sync::Arc}; use algebra::{ MuAlgebra, module::{ - Module, + Module, ModuleExt, homomorphism::{ModuleHomomorphism, MuFreeModuleHomomorphism}, }, }; @@ -501,7 +501,7 @@ pub(crate) mod secondary { use std::sync::Arc; use algebra::{ - module::{Module, homomorphism::ModuleHomomorphism}, + module::{ModuleExt, homomorphism::ModuleHomomorphism}, pair_algebra::PairAlgebra, }; use dashmap::DashMap; diff --git a/ext/src/secondary.rs b/ext/src/secondary.rs index e04b852a36..aec6fb9817 100644 --- a/ext/src/secondary.rs +++ b/ext/src/secondary.rs @@ -3,7 +3,7 @@ use std::{io, sync::Arc}; use algebra::{ Algebra, module::{ - FreeModule, Module, + FreeModule, Module, ModuleExt, homomorphism::{FreeModuleHomomorphism, ModuleHomomorphism}, }, pair_algebra::PairAlgebra, diff --git a/ext/src/yoneda.rs b/ext/src/yoneda.rs index 2fffe713c9..89f213bece 100644 --- a/ext/src/yoneda.rs +++ b/ext/src/yoneda.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use algebra::{ AdemAlgebra, Algebra, GeneratedAlgebra, MilnorAlgebra, SteenrodAlgebra, module::{ - FDModule, FreeModule, Module, QuotientModule as QM, + FDModule, FreeModule, Module, ModuleExt, QuotientModule as QM, homomorphism::{ FreeModuleHomomorphism, FullModuleHomomorphism, IdentityHomomorphism, ModuleHomomorphism, QuotientHomomorphism, QuotientHomomorphismSource, diff --git a/ext/tests/extend_identity.rs b/ext/tests/extend_identity.rs index 122967fc51..da18d06f7a 100644 --- a/ext/tests/extend_identity.rs +++ b/ext/tests/extend_identity.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use algebra::module::{ - Module, + Module, ModuleExt, homomorphism::{FullModuleHomomorphism, IdentityHomomorphism}, }; use ext::{ diff --git a/ext/tests/non_zero_min_degree.rs b/ext/tests/non_zero_min_degree.rs index 20de0f080c..aa1b414f73 100644 --- a/ext/tests/non_zero_min_degree.rs +++ b/ext/tests/non_zero_min_degree.rs @@ -1,4 +1,4 @@ -use algebra::module::Module; +use algebra::module::ModuleExt; use ext::{ chain_complex::{AugmentedChainComplex, ChainComplex}, utils::construct, diff --git a/web_ext/sseq_gui/src/actions.rs b/web_ext/sseq_gui/src/actions.rs index e1f58774d9..0184c2bd77 100644 --- a/web_ext/sseq_gui/src/actions.rs +++ b/web_ext/sseq_gui/src/actions.rs @@ -1,4 +1,4 @@ -use algebra::module::Module; +use algebra::module::{Module, ModuleExt}; use bivec::BiVec; use enum_dispatch::enum_dispatch; use ext::{CCC, chain_complex::FreeChainComplex};