use crate::scalar::Scalar;
use std::collections::BTreeMap;
type Multidegree = Vec<u128>;
pub type DpTensorKey = (Multidegree, Multidegree);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DividedPowerAlgebra {
pub(crate) dim: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DpVector<S: Scalar> {
pub(crate) terms: BTreeMap<Multidegree, S>,
}
impl<S: Scalar> DpVector<S> {
pub fn terms(&self) -> &BTreeMap<Multidegree, S> {
&self.terms
}
}
fn binom(n: u128, k: u128) -> u128 {
if k > n {
return 0;
}
let k = k.min(n - k);
let mut acc = 1u128;
for i in 0..k {
acc = acc
.checked_mul(n - i)
.expect("binomial coefficient overflows u128; use smaller exponents")
/ (i + 1);
}
acc
}
fn embed_binom<S: Scalar>(n: u128, k: u128) -> S {
let p = S::characteristic();
if p == 0 {
let b = binom(n, k);
let one = S::one();
let mut acc = S::zero();
for _ in 0..b {
acc = acc.add(&one);
}
acc
} else {
let one = S::one();
let mut result = S::one();
let mut nn = n;
let mut kk = k;
while nn != 0 || kk != 0 {
let ni = nn % p;
let ki = kk % p;
nn /= p;
kk /= p;
let digit_binom = binom(ni, ki);
let mut db_s = S::zero();
for _ in 0..digit_binom {
db_s = db_s.add(&one);
}
result = result.mul(&db_s);
if result.is_zero() {
return result;
}
}
result
}
}
impl DividedPowerAlgebra {
pub fn new(dim: usize) -> Self {
DividedPowerAlgebra { dim }
}
pub fn dim(&self) -> usize {
self.dim
}
fn empty_degree(&self) -> Multidegree {
vec![0u128; self.dim]
}
pub fn zero<S: Scalar>(&self) -> DpVector<S> {
DpVector {
terms: BTreeMap::new(),
}
}
pub fn scalar<S: Scalar>(&self, s: S) -> DpVector<S> {
let mut terms = BTreeMap::new();
if !s.is_zero() {
terms.insert(self.empty_degree(), s);
}
DpVector { terms }
}
pub fn one<S: Scalar>(&self) -> DpVector<S> {
self.scalar(S::one())
}
pub fn divided_power<S: Scalar>(&self, i: usize, k: u128) -> DpVector<S> {
assert!(i < self.dim, "generator index out of range");
let mut deg = self.empty_degree();
deg[i] = k;
let mut terms = BTreeMap::new();
if k == 0 {
return self.one();
}
terms.insert(deg, S::one());
DpVector { terms }
}
pub fn gamma1<S: Scalar>(&self, i: usize) -> DpVector<S> {
self.divided_power(i, 1)
}
pub fn monomial<S: Scalar>(&self, alpha: &[u128], coeff: S) -> DpVector<S> {
assert!(alpha.len() <= self.dim, "multidegree longer than dim");
let mut deg = self.empty_degree();
deg[..alpha.len()].copy_from_slice(alpha);
let mut terms = BTreeMap::new();
if !coeff.is_zero() {
terms.insert(deg, coeff);
}
DpVector { terms }
}
pub fn add<S: Scalar>(&self, x: &DpVector<S>, y: &DpVector<S>) -> DpVector<S> {
let mut terms = x.terms.clone();
for (deg, c) in &y.terms {
let e = terms.entry(deg.clone()).or_insert_with(S::zero);
*e = e.add(c);
if e.is_zero() {
terms.remove(deg);
}
}
DpVector { terms }
}
pub fn scalar_mul<S: Scalar>(&self, s: &S, x: &DpVector<S>) -> DpVector<S> {
let mut terms = BTreeMap::new();
for (deg, c) in &x.terms {
let v = s.mul(c);
if !v.is_zero() {
terms.insert(deg.clone(), v);
}
}
DpVector { terms }
}
pub fn mul<S: Scalar>(&self, x: &DpVector<S>, y: &DpVector<S>) -> DpVector<S> {
let mut terms: BTreeMap<Multidegree, S> = BTreeMap::new();
for (a, ca) in &x.terms {
for (b, cb) in &y.terms {
let mut sum = self.empty_degree();
let mut mult = S::one();
for i in 0..self.dim {
sum[i] = a[i] + b[i];
mult = mult.mul(&embed_binom::<S>(a[i] + b[i], a[i]));
if mult.is_zero() {
break; }
}
let coeff = ca.mul(cb).mul(&mult);
if coeff.is_zero() {
continue;
}
let e = terms.entry(sum).or_insert_with(S::zero);
*e = e.add(&coeff);
}
}
terms.retain(|_, c| !c.is_zero());
DpVector { terms }
}
pub fn coproduct<S: Scalar>(&self, x: &DpVector<S>) -> BTreeMap<DpTensorKey, S> {
let mut out: BTreeMap<DpTensorKey, S> = BTreeMap::new();
for (a, c) in &x.terms {
for beta in sub_multidegrees(a) {
let gamma: Multidegree = a.iter().zip(&beta).map(|(ai, bi)| ai - bi).collect();
let key = (beta, gamma);
let e = out.entry(key.clone()).or_insert_with(S::zero);
*e = e.add(c);
if e.is_zero() {
out.remove(&key);
}
}
}
out
}
pub fn counit<S: Scalar>(&self, x: &DpVector<S>) -> S {
x.terms
.get(&self.empty_degree())
.cloned()
.unwrap_or_else(S::zero)
}
pub fn antipode<S: Scalar>(&self, x: &DpVector<S>) -> DpVector<S> {
let mut terms = BTreeMap::new();
for (deg, c) in &x.terms {
let total: u128 = deg.iter().sum();
let v = if total % 2 == 1 { c.neg() } else { c.clone() };
terms.insert(deg.clone(), v);
}
DpVector { terms }
}
}
fn sub_multidegrees(alpha: &[u128]) -> Vec<Multidegree> {
let mut acc = vec![Vec::new()];
for &ai in alpha {
let mut next = Vec::new();
for prefix in &acc {
for bi in 0..=ai {
let mut p = prefix.clone();
p.push(bi);
next.push(p);
}
}
acc = next;
}
acc
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::{Nimber, Rational};
fn r(n: i128) -> Rational {
Rational::from_int(n)
}
fn check_counit_law<S: Scalar>(g: &DividedPowerAlgebra, x: &DpVector<S>) {
let empty = vec![0u128; g.dim];
let cop = g.coproduct(x);
let mut left = g.zero::<S>();
let mut right = g.zero::<S>();
for ((b, c), coeff) in &cop {
if *b == empty {
left = g.add(&left, &g.monomial(c, coeff.clone()));
}
if *c == empty {
right = g.add(&right, &g.monomial(b, coeff.clone()));
}
}
assert_eq!(&left, x, "(ε⊗id)∘Δ ≠ id");
assert_eq!(&right, x, "(id⊗ε)∘Δ ≠ id");
}
fn check_coassociativity<S: Scalar>(g: &DividedPowerAlgebra, x: &DpVector<S>) {
let cop = g.coproduct(x);
let mut lhs: BTreeMap<(Multidegree, Multidegree, Multidegree), S> = BTreeMap::new();
let mut rhs: BTreeMap<(Multidegree, Multidegree, Multidegree), S> = BTreeMap::new();
for ((b, c), coeff) in &cop {
for ((b1, b2), d) in &g.coproduct(&g.monomial(b, S::one())) {
let key = (b1.clone(), b2.clone(), c.clone());
let e = lhs.entry(key.clone()).or_insert_with(S::zero);
*e = e.add(&coeff.mul(d));
if e.is_zero() {
lhs.remove(&key);
}
}
for ((c1, c2), d) in &g.coproduct(&g.monomial(c, S::one())) {
let key = (b.clone(), c1.clone(), c2.clone());
let e = rhs.entry(key.clone()).or_insert_with(S::zero);
*e = e.add(&coeff.mul(d));
if e.is_zero() {
rhs.remove(&key);
}
}
}
assert_eq!(lhs, rhs, "coproduct is not coassociative");
}
fn check_antipode_axiom<S: Scalar>(g: &DividedPowerAlgebra, x: &DpVector<S>) {
let cop = g.coproduct(x);
let mut acc = g.zero::<S>();
for ((b, c), coeff) in &cop {
let sb = g.antipode(&g.monomial(b, S::one()));
let term = g.mul(&sb, &g.monomial(c, S::one()));
acc = g.add(&acc, &g.scalar_mul(coeff, &term));
}
let expect = g.scalar(g.counit(x));
assert_eq!(acc, expect, "antipode axiom failed");
}
fn run_axioms<S: Scalar>(g: &DividedPowerAlgebra, elts: &[DpVector<S>]) {
for x in elts {
check_counit_law(g, x);
check_coassociativity(g, x);
check_antipode_axiom(g, x);
}
}
fn sample<S: Scalar>(g: &DividedPowerAlgebra) -> Vec<DpVector<S>> {
vec![
g.one(),
g.gamma1(0),
g.divided_power(0, 2),
g.divided_power(1, 3),
g.mul(&g.gamma1(0), &g.divided_power(1, 2)),
g.add(&g.gamma1(0), &g.divided_power(1, 2)),
]
}
#[test]
fn hopf_axioms_rational() {
let g = DividedPowerAlgebra::new(2);
run_axioms(&g, &sample::<Rational>(&g));
}
#[test]
fn hopf_axioms_nimber() {
let g = DividedPowerAlgebra::new(2);
run_axioms(&g, &sample::<Nimber>(&g));
}
#[test]
fn generators_are_primitive() {
let g = DividedPowerAlgebra::new(2);
let cop = g.coproduct(&g.gamma1::<Rational>(0));
assert_eq!(cop.len(), 2);
assert_eq!(cop.get(&(vec![1, 0], vec![0, 0])), Some(&r(1)));
assert_eq!(cop.get(&(vec![0, 0], vec![1, 0])), Some(&r(1)));
}
#[test]
fn binomial_product_over_rationals() {
let g = DividedPowerAlgebra::new(1);
let prod = g.mul(&g.gamma1::<Rational>(0), &g.divided_power(0, 2));
assert_eq!(prod.terms.get(&vec![3]), Some(&r(3)));
let sq = g.mul(&g.gamma1::<Rational>(0), &g.gamma1(0));
assert_eq!(sq.terms.get(&vec![2]), Some(&r(2)));
}
#[test]
fn divided_square_vanishes_in_char_two() {
let g = DividedPowerAlgebra::new(1);
let sq = g.mul(&g.gamma1::<Nimber>(0), &g.gamma1(0));
assert!(sq.terms.is_empty(), "(γ^[1])² must vanish in char 2");
let dp2 = g.divided_power::<Nimber>(0, 2);
assert_eq!(dp2.terms.get(&vec![2]), Some(&Nimber(1)));
let sq2 = g.mul(&dp2, &dp2);
assert!(sq2.terms.is_empty(), "C(4,2)=6 ≡ 0 mod 2");
}
#[test]
fn large_exponent_in_char_two_terminates() {
use crate::scalar::Nimber;
let g = DividedPowerAlgebra::new(1);
let dp100 = g.divided_power::<Nimber>(0, 100);
let product = g.mul(&dp100, &dp100);
assert!(
product.terms.is_empty(),
"C(200,100) must be 0 mod 2 (by Lucas), product must vanish"
);
}
#[test]
fn moderate_exponent_in_char_zero_correct() {
let g = DividedPowerAlgebra::new(1);
let dp5 = g.divided_power::<Rational>(0, 5);
let product = g.mul(&dp5, &dp5);
assert_eq!(product.terms.get(&vec![10]), Some(&r(252)));
}
#[test]
fn lucas_theorem_mod_three() {
use crate::scalar::Fp;
type F3 = Fp<3>;
let g = DividedPowerAlgebra::new(1);
let dp2 = g.divided_power::<F3>(0, 2);
let product = g.mul(&dp2, &dp2); assert!(
product.terms.is_empty(),
"C(4,2)=6 ≡ 0 (mod 3); product must vanish"
);
let dp3 = g.divided_power::<F3>(0, 3);
let prod2 = g.mul(&dp2, &dp3); assert_eq!(
prod2.terms.get(&vec![5]),
Some(&F3::one()),
"C(5,2)=10≡1 (mod 3)"
);
}
}