use crate::error::{SpecialError, SpecialResult};
use crate::theta_functions::{theta2, theta3};
const Q_SERIES_TOL: f64 = 1e-16;
const MAX_Q_TERMS: usize = 1000;
const MAX_ETA_TERMS: usize = 1500;
#[inline]
fn validate_tau_imag(tau_imag: f64) -> SpecialResult<()> {
if !tau_imag.is_finite() || tau_imag <= 0.0 {
return Err(SpecialError::DomainError(format!(
"tau_imag must be strictly positive and finite, got {tau_imag}"
)));
}
Ok(())
}
#[inline]
fn compute_q(tau_imag: f64) -> f64 {
(-2.0 * std::f64::consts::PI * tau_imag).exp()
}
fn divisor_sum(n: u64, k: u32) -> f64 {
if n == 0 {
return 0.0;
}
if n == 1 {
return 1.0;
}
let mut sum = 0.0_f64;
let sqrt_n = (n as f64).sqrt() as u64;
for d in 1..=sqrt_n {
if n.is_multiple_of(d) {
sum += (d as f64).powi(k as i32);
let complement = n / d;
if complement != d {
sum += (complement as f64).powi(k as i32);
}
}
}
sum
}
fn eisenstein_q_series(q: f64, k: u32, max_terms: usize) -> f64 {
if q < 1e-300 {
return 0.0;
}
let mut sum = 0.0_f64;
let mut q_power = q;
for n in 1..=max_terms {
let sigma = divisor_sum(n as u64, k);
let term = sigma * q_power;
sum += term;
if term.abs() < Q_SERIES_TOL * sum.abs().max(1.0) {
break;
}
q_power *= q;
if q_power < 1e-300 {
break;
}
}
sum
}
pub fn eisenstein_e2(tau_imag: f64) -> SpecialResult<f64> {
validate_tau_imag(tau_imag)?;
let q = compute_q(tau_imag);
let series_sum = eisenstein_q_series(q, 1, MAX_Q_TERMS);
Ok(1.0 - 24.0 * series_sum)
}
pub fn eisenstein_e4(tau_imag: f64) -> SpecialResult<f64> {
validate_tau_imag(tau_imag)?;
let q = compute_q(tau_imag);
let series_sum = eisenstein_q_series(q, 3, MAX_Q_TERMS);
Ok(1.0 + 240.0 * series_sum)
}
pub fn eisenstein_e6(tau_imag: f64) -> SpecialResult<f64> {
validate_tau_imag(tau_imag)?;
let q = compute_q(tau_imag);
let series_sum = eisenstein_q_series(q, 5, MAX_Q_TERMS);
Ok(1.0 - 504.0 * series_sum)
}
pub fn dedekind_eta(tau_imag: f64) -> SpecialResult<f64> {
validate_tau_imag(tau_imag)?;
let q = compute_q(tau_imag);
let q_24th = (-std::f64::consts::PI * tau_imag / 12.0).exp();
if q_24th < 1e-300 {
return Ok(0.0);
}
let mut product = 1.0_f64;
let mut q_power = q;
for _n in 1..=MAX_ETA_TERMS {
if q_power < 1e-300 {
break;
}
let factor = 1.0 - q_power;
product *= factor;
if (1.0 - factor).abs() < Q_SERIES_TOL {
break;
}
q_power *= q;
}
Ok(q_24th * product)
}
pub fn klein_j_invariant(tau_imag: f64) -> SpecialResult<f64> {
validate_tau_imag(tau_imag)?;
let e4 = eisenstein_e4(tau_imag)?;
let e6 = eisenstein_e6(tau_imag)?;
let e4_cubed = e4 * e4 * e4;
let e6_squared = e6 * e6;
let delta_normalized = e4_cubed - e6_squared;
if delta_normalized.abs() < 1e-300 {
return Err(SpecialError::ComputationError(
"Discriminant vanished in j-invariant computation".to_string(),
));
}
Ok(1728.0 * e4_cubed / delta_normalized)
}
pub fn modular_lambda(tau_imag: f64) -> SpecialResult<f64> {
validate_tau_imag(tau_imag)?;
let q_nome = (-std::f64::consts::PI * tau_imag).exp();
if q_nome >= 1.0 {
return Err(SpecialError::DomainError(
"Nome q must be in [0, 1) for theta functions".to_string(),
));
}
let t2 = theta2(0.0, q_nome);
let t3 = theta3(0.0, q_nome);
if t3.abs() < 1e-300 {
return Err(SpecialError::ComputationError(
"theta3 vanished in modular lambda computation".to_string(),
));
}
let ratio = t2 / t3;
let ratio_sq = ratio * ratio;
Ok(ratio_sq * ratio_sq) }
pub fn modular_discriminant(tau_imag: f64) -> SpecialResult<f64> {
validate_tau_imag(tau_imag)?;
let eta = dedekind_eta(tau_imag)?;
let eta_sq = eta * eta;
let eta_4 = eta_sq * eta_sq;
let eta_8 = eta_4 * eta_4;
let eta_16 = eta_8 * eta_8;
let eta_24 = eta_16 * eta_8;
Ok(eta_24)
}
pub fn modular_discriminant_eisenstein(tau_imag: f64) -> SpecialResult<f64> {
validate_tau_imag(tau_imag)?;
let e4 = eisenstein_e4(tau_imag)?;
let e6 = eisenstein_e6(tau_imag)?;
let e4_cubed = e4 * e4 * e4;
let e6_squared = e6 * e6;
Ok((e4_cubed - e6_squared) / 1728.0)
}
pub fn lattice_invariants_from_tau(tau_imag: f64) -> SpecialResult<(f64, f64)> {
validate_tau_imag(tau_imag)?;
let e4 = eisenstein_e4(tau_imag)?;
let e6 = eisenstein_e6(tau_imag)?;
let two_pi = 2.0 * std::f64::consts::PI;
let two_pi_sq = two_pi * two_pi;
let two_pi_4 = two_pi_sq * two_pi_sq;
let two_pi_6 = two_pi_4 * two_pi_sq;
let g2 = two_pi_4 / 12.0 * e4;
let g3 = two_pi_6 / 216.0 * e6;
Ok((g2, g3))
}
pub fn ramanujan_tau(n: u64) -> SpecialResult<i64> {
if n == 0 {
return Err(SpecialError::DomainError(
"Ramanujan tau function is defined for n >= 1".to_string(),
));
}
let known_values: &[i64] = &[
1, -24, 252, -1472, 4830, -6048, -16744, 84480, -113643, -115920, 534612, -370944, -577738, 401856, 1217160, 987136, -6905934, 2727432, 10661420, -7109760, -4219488, -12830688, 18643272, 21288960, -25499225, 13865712, -73279080, 24647168, 128406630, -29211840, ];
if (n as usize) <= known_values.len() {
Ok(known_values[(n as usize) - 1])
} else {
Err(SpecialError::NotImplementedError(format!(
"Ramanujan tau function for n = {n} > 30 is not precomputed"
)))
}
}
pub fn eta_log_derivative(tau_imag: f64) -> SpecialResult<f64> {
validate_tau_imag(tau_imag)?;
let e2 = eisenstein_e2(tau_imag)?;
Ok(-std::f64::consts::PI / 12.0 * e2)
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f64 = 1e-10;
const LOOSE_EPSILON: f64 = 1e-6;
#[test]
fn test_tau_imag_zero_is_error() {
assert!(dedekind_eta(0.0).is_err());
assert!(klein_j_invariant(0.0).is_err());
assert!(modular_lambda(0.0).is_err());
assert!(eisenstein_e2(0.0).is_err());
assert!(eisenstein_e4(0.0).is_err());
assert!(eisenstein_e6(0.0).is_err());
assert!(modular_discriminant(0.0).is_err());
assert!(lattice_invariants_from_tau(0.0).is_err());
}
#[test]
fn test_tau_imag_negative_is_error() {
assert!(dedekind_eta(-1.0).is_err());
assert!(klein_j_invariant(-0.5).is_err());
assert!(modular_lambda(-2.0).is_err());
assert!(eisenstein_e4(-1.0).is_err());
}
#[test]
fn test_tau_imag_nan_is_error() {
assert!(dedekind_eta(f64::NAN).is_err());
assert!(eisenstein_e4(f64::NAN).is_err());
}
#[test]
fn test_tau_imag_infinity_is_error() {
assert!(dedekind_eta(f64::INFINITY).is_err());
assert!(eisenstein_e4(f64::INFINITY).is_err());
}
#[test]
fn test_dedekind_eta_at_i() {
let eta = dedekind_eta(1.0).expect("valid input");
let expected = 0.768_225_514_159;
assert!(
(eta - expected).abs() < 1e-6,
"eta(i) = {eta}, expected ≈ {expected}"
);
}
#[test]
fn test_dedekind_eta_positive() {
for &t in &[0.1, 0.5, 1.0, 2.0, 5.0] {
let eta = dedekind_eta(t).expect("valid input");
assert!(eta > 0.0, "eta({t}) = {eta} should be positive");
}
}
#[test]
fn test_klein_j_at_i() {
let j = klein_j_invariant(1.0).expect("valid input");
assert!(
(j - 1728.0).abs() < LOOSE_EPSILON,
"j(i) = {j}, expected 1728.0"
);
}
#[test]
fn test_klein_j_large_tau() {
let j = klein_j_invariant(2.0).expect("valid input");
let q = (-2.0 * std::f64::consts::PI * 2.0).exp();
let expected_approx = 1.0 / q + 744.0;
assert!(
(j - expected_approx).abs() / expected_approx.abs() < 1e-2,
"j(2i) = {j}, expected ≈ {expected_approx}"
);
}
#[test]
fn test_modular_lambda_at_i() {
let lam = modular_lambda(1.0).expect("valid input");
assert!(
(lam - 0.5).abs() < EPSILON,
"lambda(i) = {lam}, expected 0.5"
);
}
#[test]
fn test_modular_lambda_range() {
for &t in &[0.3, 0.5, 1.0, 2.0, 5.0] {
let lam = modular_lambda(t).expect("valid input");
assert!(
lam > 0.0 && lam < 1.0,
"lambda({t}) = {lam} should be in (0,1)"
);
}
}
#[test]
fn test_eisenstein_large_tau() {
let e2 = eisenstein_e2(10.0).expect("valid input");
let e4 = eisenstein_e4(10.0).expect("valid input");
let e6 = eisenstein_e6(10.0).expect("valid input");
assert!(
(e2 - 1.0).abs() < EPSILON,
"E_2(10i) = {e2}, expected ≈ 1.0"
);
assert!(
(e4 - 1.0).abs() < EPSILON,
"E_4(10i) = {e4}, expected ≈ 1.0"
);
assert!(
(e6 - 1.0).abs() < EPSILON,
"E_6(10i) = {e6}, expected ≈ 1.0"
);
}
#[test]
fn test_eisenstein_e4_at_i() {
let e4 = eisenstein_e4(1.0).expect("valid input");
assert!(e4 > 1.0, "E_4(i) should be > 1, got {e4}");
assert!(e4.is_finite(), "E_4(i) should be finite");
}
#[test]
fn test_eisenstein_e6_at_i() {
let e6 = eisenstein_e6(1.0).expect("valid input");
assert!(e6.abs() < 1e-8, "E_6(i) should be 0 by symmetry, got {e6}");
}
#[test]
fn test_j_from_eisenstein_identity() {
for &t in &[0.5, 1.0, 1.5, 2.0] {
let e4 = eisenstein_e4(t).expect("valid input");
let e6 = eisenstein_e6(t).expect("valid input");
let j_direct = klein_j_invariant(t).expect("valid input");
let e4_cubed = e4 * e4 * e4;
let e6_squared = e6 * e6;
let j_from_eisenstein = 1728.0 * e4_cubed / (e4_cubed - e6_squared);
assert!(
(j_direct - j_from_eisenstein).abs() / j_direct.abs().max(1.0) < 1e-10,
"j-invariant cross-check failed at tau_imag={t}: {j_direct} vs {j_from_eisenstein}"
);
}
}
#[test]
fn test_delta_eta_eisenstein_identity() {
for &t in &[0.5, 1.0, 1.5, 2.0, 3.0] {
let delta_eta = modular_discriminant(t).expect("valid input");
let delta_eis = modular_discriminant_eisenstein(t).expect("valid input");
let rel_err = if delta_eta.abs() > 1e-300 {
(delta_eta - delta_eis).abs() / delta_eta.abs()
} else {
(delta_eta - delta_eis).abs()
};
assert!(
rel_err < 1e-6,
"Delta cross-check failed at tau_imag={t}: eta^24={delta_eta}, \
(E4^3-E6^2)/1728={delta_eis}, rel_err={rel_err}"
);
}
}
#[test]
fn test_lattice_invariants_finite() {
let (g2, g3) = lattice_invariants_from_tau(1.0).expect("valid input");
assert!(g2.is_finite(), "g2 should be finite, got {g2}");
assert!(g3.is_finite(), "g3 should be finite, got {g3}");
}
#[test]
fn test_lattice_invariants_g3_zero_at_i() {
let (_g2, g3) = lattice_invariants_from_tau(1.0).expect("valid input");
assert!(
g3.abs() < 1e-4,
"g3 should be ≈ 0 at tau = i (since E_6(i) = 0), got {g3}"
);
}
#[test]
fn test_lattice_invariants_discriminant_positive() {
for &t in &[0.5, 1.0, 2.0] {
let (g2, g3) = lattice_invariants_from_tau(t).expect("valid input");
let disc = g2 * g2 * g2 - 27.0 * g3 * g3;
assert!(
disc > 0.0,
"Discriminant g2^3 - 27*g3^2 should be positive at tau_imag={t}, got {disc}"
);
}
}
#[test]
fn test_divisor_sum_basic() {
assert!((divisor_sum(1, 1) - 1.0).abs() < EPSILON);
assert!((divisor_sum(6, 1) - 12.0).abs() < EPSILON);
assert!((divisor_sum(4, 3) - 73.0).abs() < EPSILON);
assert!((divisor_sum(12, 0) - 6.0).abs() < EPSILON);
}
#[test]
fn test_divisor_sum_primes() {
assert!((divisor_sum(7, 1) - 8.0).abs() < EPSILON); assert!((divisor_sum(7, 3) - 344.0).abs() < EPSILON); assert!((divisor_sum(13, 5) - 371_294.0).abs() < EPSILON); }
#[test]
fn test_ramanujan_tau_known() {
assert_eq!(ramanujan_tau(1).expect("valid"), 1);
assert_eq!(ramanujan_tau(2).expect("valid"), -24);
assert_eq!(ramanujan_tau(3).expect("valid"), 252);
assert_eq!(ramanujan_tau(4).expect("valid"), -1472);
assert_eq!(ramanujan_tau(5).expect("valid"), 4830);
}
#[test]
fn test_ramanujan_tau_zero_is_error() {
assert!(ramanujan_tau(0).is_err());
}
#[test]
fn test_ramanujan_tau_too_large() {
assert!(ramanujan_tau(31).is_err());
}
#[test]
fn test_eta_log_derivative_finite() {
let deriv = eta_log_derivative(1.0).expect("valid input");
assert!(deriv.is_finite(), "eta log derivative should be finite");
}
#[test]
fn test_very_large_tau_imag() {
let t = 100.0;
let e2 = eisenstein_e2(t).expect("valid input");
let e4 = eisenstein_e4(t).expect("valid input");
let e6 = eisenstein_e6(t).expect("valid input");
assert!((e2 - 1.0).abs() < 1e-15, "E_2 should be ≈ 1 for large tau");
assert!((e4 - 1.0).abs() < 1e-15, "E_4 should be ≈ 1 for large tau");
assert!((e6 - 1.0).abs() < 1e-15, "E_6 should be ≈ 1 for large tau");
}
#[test]
fn test_j_invariant_at_various_tau() {
let j_15 = klein_j_invariant(1.5).expect("valid input");
let j_20 = klein_j_invariant(2.0).expect("valid input");
assert!(j_15 > 1728.0, "j(1.5i) should be > 1728, got {j_15}");
assert!(j_20 > j_15, "j(2i) should be > j(1.5i): {j_20} vs {j_15}");
}
}