use crate::linalg::field::inverse_matrix;
use crate::scalar::{Rational, Scalar};
fn eisenstein_constant_rational(k: i128) -> Rational {
let n = usize::try_from(2 * k).expect("2k fits usize");
let (num, den) = crate::forms::integral::mass_formula::bernoulli(n)
.expect("Bernoulli B_{2k} within the i128 model");
let numerator = (-4 * k)
.checked_mul(den)
.expect("Eisenstein constant numerator exceeds i128");
Rational::new(numerator, num)
}
fn eisenstein_constant(k: i128) -> i128 {
let c = eisenstein_constant_rational(k);
assert!(c.is_integer(), "Eisenstein constant is integral here");
c.numer()
}
fn sigma_power(n: usize, power: u32) -> i128 {
let mut out = 0i128;
for d in 1..=n {
if n.is_multiple_of(d) {
let dp = (d as i128)
.checked_pow(power)
.expect("divisor power exceeds i128 (see sigma_power's documented n cap)");
out = out.checked_add(dp).expect("divisor-power sum exceeds i128");
}
}
out
}
fn qexp_add(a: &[Rational], b: &[Rational], terms: usize) -> Vec<Rational> {
(0..terms)
.map(|i| a[i].add(&b[i]))
.collect::<Vec<Rational>>()
}
fn qexp_sub(a: &[Rational], b: &[Rational], terms: usize) -> Vec<Rational> {
(0..terms)
.map(|i| a[i].sub(&b[i]))
.collect::<Vec<Rational>>()
}
fn qexp_scale(a: &[Rational], c: Rational, terms: usize) -> Vec<Rational> {
(0..terms).map(|i| a[i].mul(&c)).collect()
}
fn qexp_mul(a: &[Rational], b: &[Rational], terms: usize) -> Vec<Rational> {
let mut out = vec![Rational::zero(); terms];
for (i, ai) in a.iter().enumerate().take(terms) {
if ai.is_zero() {
continue;
}
for (j, bj) in b.iter().enumerate().take(terms - i) {
if bj.is_zero() {
continue;
}
out[i + j] = out[i + j].add(&ai.mul(bj));
}
}
out
}
fn qexp_pow(base: &[Rational], exp: usize, terms: usize) -> Vec<Rational> {
let mut out = vec![Rational::zero(); terms];
if terms == 0 {
return out;
}
out[0] = Rational::one();
for _ in 0..exp {
out = qexp_mul(&out, base, terms);
}
out
}
pub fn qexp_from_int(coeffs: &[i128]) -> Vec<Rational> {
coeffs.iter().map(|&x| Rational::from_int(x)).collect()
}
pub fn eisenstein_e4(terms: usize) -> Vec<Rational> {
let mut out = vec![Rational::zero(); terms];
if terms == 0 {
return out;
}
out[0] = Rational::one();
let c4 = eisenstein_constant(2); for (n, coeff) in out.iter_mut().enumerate().skip(1) {
*coeff = Rational::from_int(
c4.checked_mul(sigma_power(n, 3))
.expect("E4 coefficient exceeds i128"),
);
}
out
}
pub fn eisenstein_e6(terms: usize) -> Vec<Rational> {
let mut out = vec![Rational::zero(); terms];
if terms == 0 {
return out;
}
out[0] = Rational::one();
let c6 = eisenstein_constant(3); for (n, coeff) in out.iter_mut().enumerate().skip(1) {
*coeff = Rational::from_int(
c6.checked_mul(sigma_power(n, 5))
.expect("E6 coefficient exceeds i128"),
);
}
out
}
pub fn eisenstein_e12(terms: usize) -> Vec<Rational> {
let mut out = vec![Rational::zero(); terms];
if terms == 0 {
return out;
}
out[0] = Rational::one();
let c12 = eisenstein_constant_rational(6);
for (n, coeff) in out.iter_mut().enumerate().skip(1) {
*coeff = c12.mul(&Rational::from_int(sigma_power(n, 11)));
}
out
}
pub fn delta(terms: usize) -> Vec<Rational> {
let e4 = eisenstein_e4(terms);
let e6 = eisenstein_e6(terms);
let e4_3 = qexp_pow(&e4, 3, terms);
let e6_2 = qexp_pow(&e6, 2, terms);
qexp_scale(
&qexp_sub(&e4_3, &e6_2, terms),
Rational::new(1, 1728),
terms,
)
}
pub fn mk_basis(weight: usize, terms: usize) -> Vec<Vec<Rational>> {
if terms == 0 {
return Vec::new();
}
if weight == 0 {
let mut one = vec![Rational::zero(); terms];
one[0] = Rational::one();
return vec![one];
}
let e4 = eisenstein_e4(terms);
let e6 = eisenstein_e6(terms);
let mut basis = Vec::new();
for b in 0..=weight / 6 {
let rem = weight - 6 * b;
if rem.is_multiple_of(4) {
let a = rem / 4;
let e4a = qexp_pow(&e4, a, terms);
let e6b = qexp_pow(&e6, b, terms);
basis.push(qexp_mul(&e4a, &e6b, terms));
}
}
basis
}
pub fn as_modular_form(
q_expansion: &[Rational],
weight: usize,
terms: usize,
) -> Option<Vec<Rational>> {
if q_expansion.len() < terms {
return None;
}
let basis = mk_basis(weight, terms);
let dim = basis.len();
if dim == 0 {
return (0..terms).all(|i| q_expansion[i].is_zero()).then(Vec::new);
}
if terms < dim {
return None;
}
let mut matrix = vec![vec![Rational::zero(); dim]; dim];
for row in 0..dim {
for col in 0..dim {
matrix[row][col] = basis[col][row].clone();
}
}
let inv = inverse_matrix(matrix)?;
let mut coords = vec![Rational::zero(); dim];
for row in 0..dim {
for col in 0..dim {
coords[row] = coords[row].add(&inv[row][col].mul(&q_expansion[col]));
}
}
for i in 0..terms {
let mut got = Rational::zero();
for (coord, b) in coords.iter().zip(&basis) {
got = got.add(&coord.mul(&b[i]));
}
if got != q_expansion[i] {
return None;
}
}
Some(coords)
}
pub fn modular_qexp_add(a: &[Rational], b: &[Rational], terms: usize) -> Vec<Rational> {
qexp_add(a, b, terms)
}
pub fn modular_qexp_sub(a: &[Rational], b: &[Rational], terms: usize) -> Vec<Rational> {
qexp_sub(a, b, terms)
}
pub fn modular_qexp_mul(a: &[Rational], b: &[Rational], terms: usize) -> Vec<Rational> {
qexp_mul(a, b, terms)
}
pub fn modular_qexp_scale(a: &[Rational], c: Rational, terms: usize) -> Vec<Rational> {
qexp_scale(a, c, terms)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eisenstein_series_start_with_standard_coefficients() {
assert_eq!(
eisenstein_e4(5),
qexp_from_int(&[1, 240, 2160, 6720, 17520])
);
assert_eq!(eisenstein_e6(4), qexp_from_int(&[1, -504, -16632, -122976]));
assert_eq!(
eisenstein_e12(3),
vec![
Rational::one(),
Rational::new(65_520, 691),
Rational::new(134_250_480, 691),
]
);
assert_eq!(delta(4), qexp_from_int(&[0, 1, -24, 252]));
}
#[test]
fn eisenstein_constants_derive_from_the_shared_bernoulli_source() {
assert_eq!(eisenstein_constant(2), 240, "c₄ = −8/B₄");
assert_eq!(eisenstein_constant(3), -504, "c₆ = −12/B₆");
assert_eq!(
eisenstein_constant_rational(6),
Rational::new(65_520, 691),
"c₁₂ = −24/B₁₂"
);
use crate::forms::integral::mass_formula::bernoulli;
assert_eq!(bernoulli(2), Some((1, 6)));
assert_eq!(bernoulli(4), Some((-1, 30)));
assert_eq!(bernoulli(6), Some((1, 42)));
assert_eq!(bernoulli(8), Some((-1, 30)));
}
#[test]
fn sigma_power_stays_exact_up_to_the_documented_cap() {
assert_eq!(
sigma_power(2989, 11),
170_131_631_069_539_054_464_162_161_472_679_926_966
);
}
#[test]
#[should_panic(expected = "divisor power exceeds i128")]
fn sigma_power_panics_past_the_documented_cap_instead_of_wrapping() {
let _ = sigma_power(2990, 11);
}
#[test]
fn modular_identification_solves_exact_coordinates() {
let e4 = eisenstein_e4(5);
assert_eq!(as_modular_form(&e4, 4, 5), Some(vec![Rational::one()]));
let e4_squared = modular_qexp_mul(&e4, &e4, 5);
assert_eq!(
as_modular_form(&e4_squared, 8, 5),
Some(vec![Rational::one()])
);
let e4_cubed = modular_qexp_mul(&e4_squared, &e4, 3);
let leech_form = modular_qexp_sub(
&e4_cubed,
&modular_qexp_scale(&delta(3), Rational::from_int(720), 3),
3,
);
assert_eq!(
as_modular_form(&leech_form, 12, 3),
Some(vec![Rational::new(7, 12), Rational::new(5, 12)])
);
}
}