use crate::error::{SpecialError, SpecialResult};
use std::f64::consts::PI;
const MAX_TERMS: usize = 500;
const TOL: f64 = 1e-15;
pub fn dedekind_eta(tau_imag: f64) -> SpecialResult<f64> {
if tau_imag <= 0.0 {
return Err(SpecialError::DomainError(format!(
"dedekind_eta: tau_imag must be positive, got {tau_imag}"
)));
}
let q = (-2.0 * PI * tau_imag).exp();
if q >= 1.0 {
return Err(SpecialError::ComputationError(
"dedekind_eta: nome q >= 1 (tau_imag too small)".to_string(),
));
}
let prefactor = (-2.0 * PI * tau_imag / 24.0).exp();
let mut product = 1.0f64;
let mut qn = q;
for _ in 1..=MAX_TERMS {
let factor = 1.0 - qn;
product *= factor;
if (1.0 - factor).abs() < TOL {
break;
}
qn *= q;
}
Ok(prefactor * product)
}
pub fn lambert_series(q: f64, n_terms: usize) -> SpecialResult<f64> {
if q <= 0.0 || q >= 1.0 {
return Err(SpecialError::DomainError(format!(
"lambert_series: q must satisfy 0 < q < 1, got {q}"
)));
}
if n_terms == 0 {
return Ok(0.0);
}
let mut sum = 0.0f64;
let mut qn = q;
for _ in 1..=n_terms {
let denom = 1.0 - qn;
if denom.abs() < 1e-300 {
break;
}
let term = qn / denom;
sum += term;
if term.abs() < TOL * sum.abs().max(1e-300) {
break;
}
qn *= q;
}
Ok(sum)
}
pub fn eisenstein_g4(tau_imag: f64) -> SpecialResult<f64> {
if tau_imag <= 0.0 {
return Err(SpecialError::DomainError(format!(
"eisenstein_g4: tau_imag must be positive, got {tau_imag}"
)));
}
let q = (-2.0 * PI * tau_imag).exp();
if q >= 1.0 {
return Err(SpecialError::ComputationError(
"eisenstein_g4: nome q >= 1".to_string(),
));
}
let zeta4_2 = PI.powi(4) / 45.0;
let coeff = 8.0 * PI.powi(4) / 3.0;
let series = eisenstein_sigma_series(q, 3, MAX_TERMS)?;
Ok(zeta4_2 + coeff * series)
}
pub fn eisenstein_g6(tau_imag: f64) -> SpecialResult<f64> {
if tau_imag <= 0.0 {
return Err(SpecialError::DomainError(format!(
"eisenstein_g6: tau_imag must be positive, got {tau_imag}"
)));
}
let q = (-2.0 * PI * tau_imag).exp();
if q >= 1.0 {
return Err(SpecialError::ComputationError(
"eisenstein_g6: nome q >= 1".to_string(),
));
}
let zeta6_2 = 2.0 * PI.powi(6) / 945.0;
let coeff = 16.0 * PI.powi(6) / 15.0;
let series = eisenstein_sigma_series(q, 5, MAX_TERMS)?;
Ok(zeta6_2 + coeff * series)
}
fn eisenstein_sigma_series(q: f64, k: u32, n_terms: usize) -> SpecialResult<f64> {
let mut sum = 0.0f64;
let mut qn = q;
for n in 1..=n_terms {
let sigma_k = divisor_power_sum(n as u64, k);
let term = sigma_k * qn;
sum += term;
if term.abs() < TOL * sum.abs().max(1e-300) {
break;
}
qn *= q;
}
Ok(sum)
}
fn divisor_power_sum(n: u64, k: u32) -> f64 {
let mut sum = 0.0f64;
let mut d = 1u64;
while d * d <= n {
if n % d == 0 {
sum += (d as f64).powi(k as i32);
if d != n / d {
sum += ((n / d) as f64).powi(k as i32);
}
}
d += 1;
}
sum
}
pub fn klein_j_invariant(tau_imag: f64) -> SpecialResult<f64> {
let g4 = eisenstein_g4(tau_imag)?;
let g6 = eisenstein_g6(tau_imag)?;
let g2 = 60.0 * g4;
let g3 = 140.0 * g6;
let g2_cubed = g2.powi(3);
let discriminant = g2_cubed - 27.0 * g3.powi(2);
if discriminant.abs() < 1e-300 {
return Err(SpecialError::ComputationError(
"klein_j_invariant: discriminant is zero (singular lattice)".to_string(),
));
}
Ok(1728.0 * g2_cubed / discriminant)
}
pub fn weierstrass_p(z: f64, g2: f64, g3: f64, n_terms: usize) -> SpecialResult<f64> {
if z == 0.0 {
return Err(SpecialError::DomainError(
"weierstrass_p: z must be nonzero (pole at z=0)".to_string(),
));
}
let g4 = g2 / 60.0;
let g6 = g3 / 140.0;
let actual_terms = n_terms.min(50).max(2);
let mut c = vec![0.0f64; actual_terms + 2];
if actual_terms >= 1 {
c[1] = g2 / 20.0;
}
if actual_terms >= 2 {
c[2] = g3 / 28.0;
}
for n in 3..=actual_terms {
let mut s = 0.0f64;
for m in 1..=(n - 2) {
s += c[m] * c[n - 1 - m];
}
let nd = n as f64;
c[n] = 3.0 / ((2.0 * nd + 3.0) * (nd - 1.0)) * s;
}
let z2 = z * z;
let mut result = 1.0 / z2;
let mut zn = z2; for n in 1..=actual_terms {
result += c[n] * zn;
zn *= z2;
}
Ok(result)
}
pub fn weierstrass_p_prime(z: f64, g2: f64, g3: f64, n_terms: usize) -> SpecialResult<f64> {
if z == 0.0 {
return Err(SpecialError::DomainError(
"weierstrass_p_prime: z must be nonzero (pole at z=0)".to_string(),
));
}
let actual_terms = n_terms.min(50).max(2);
let mut c = vec![0.0f64; actual_terms + 2];
if actual_terms >= 1 {
c[1] = g2 / 20.0;
}
if actual_terms >= 2 {
c[2] = g3 / 28.0;
}
for n in 3..=actual_terms {
let mut s = 0.0f64;
for m in 1..=(n - 2) {
s += c[m] * c[n - 1 - m];
}
let nd = n as f64;
c[n] = 3.0 / ((2.0 * nd + 3.0) * (nd - 1.0)) * s;
}
let z2 = z * z;
let mut result = -2.0 / (z2 * z);
let mut zn = z; for n in 1..=actual_terms {
let nd = n as f64;
result += 2.0 * nd * c[n] * zn;
zn *= z2;
}
Ok(result)
}
pub fn lattice_theta1(z: f64, q: f64) -> SpecialResult<f64> {
validate_nome(q)?;
let mut sum = 0.0f64;
for n in 0..MAX_TERMS {
let nf = n as f64;
let half = nf + 0.5;
let sign = if n % 2 == 0 { 1.0 } else { -1.0 };
let term = sign * q.powf(half * half) * ((2.0 * nf + 1.0) * z).sin();
sum += term;
if term.abs() < TOL {
break;
}
}
Ok(2.0 * sum)
}
pub fn lattice_theta2(z: f64, q: f64) -> SpecialResult<f64> {
validate_nome(q)?;
let mut sum = 0.0f64;
for n in 0..MAX_TERMS {
let nf = n as f64;
let half = nf + 0.5;
let term = q.powf(half * half) * ((2.0 * nf + 1.0) * z).cos();
sum += term;
if term.abs() < TOL {
break;
}
}
Ok(2.0 * sum)
}
pub fn lattice_theta3(z: f64, q: f64) -> SpecialResult<f64> {
validate_nome(q)?;
let mut sum = 0.0f64;
for n in 1..=MAX_TERMS {
let nf = n as f64;
let term = q.powf(nf * nf) * (2.0 * nf * z).cos();
sum += term;
if term.abs() < TOL {
break;
}
}
Ok(1.0 + 2.0 * sum)
}
pub fn lattice_theta4(z: f64, q: f64) -> SpecialResult<f64> {
validate_nome(q)?;
let mut sum = 0.0f64;
for n in 1..=MAX_TERMS {
let nf = n as f64;
let sign = if n % 2 == 0 { 1.0 } else { -1.0 };
let term = sign * q.powf(nf * nf) * (2.0 * nf * z).cos();
sum += term;
if term.abs() < TOL {
break;
}
}
Ok(1.0 + 2.0 * sum)
}
fn validate_nome(q: f64) -> SpecialResult<()> {
if q < 0.0 || q >= 1.0 {
return Err(SpecialError::DomainError(format!(
"lattice: nome q must satisfy 0 ≤ q < 1, got {q}"
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_dedekind_eta_positive() {
let eta = dedekind_eta(1.0).expect("should not fail");
assert!((eta - 0.768_225_142_896).abs() < 1e-5);
}
#[test]
fn test_dedekind_eta_invalid() {
assert!(dedekind_eta(0.0).is_err());
assert!(dedekind_eta(-1.0).is_err());
}
#[test]
fn test_lambert_series_convergence() {
let v = lambert_series(0.3, 300).expect("should not fail");
assert!(v > 0.0 && v.is_finite());
}
#[test]
fn test_lambert_series_small_q() {
let q = 0.01f64;
let v = lambert_series(q, 300).expect("should not fail");
assert!((v - q / (1.0 - q)).abs() < 1e-6);
}
#[test]
fn test_lambert_series_invalid() {
assert!(lambert_series(0.0, 100).is_err());
assert!(lambert_series(1.0, 100).is_err());
assert!(lambert_series(-0.5, 100).is_err());
}
#[test]
fn test_eisenstein_g4_finite() {
let g4 = eisenstein_g4(1.0).expect("should not fail");
assert!(g4.is_finite() && g4 > 0.0);
}
#[test]
fn test_eisenstein_g6_finite() {
let g6 = eisenstein_g6(1.0).expect("should not fail");
assert!(g6.is_finite() && g6 > 0.0);
}
#[test]
fn test_eisenstein_decreasing_with_t() {
let g4_1 = eisenstein_g4(1.0).expect("ok");
let g4_2 = eisenstein_g4(2.0).expect("ok");
assert!(g4_2.is_finite());
let _ = g4_1; }
#[test]
fn test_klein_j_invariant_at_i() {
let j = klein_j_invariant(1.0).expect("should not fail");
assert!((j - 1728.0).abs() < 1.0, "j(i) = {j}, expected 1728");
}
#[test]
fn test_klein_j_invalid() {
assert!(klein_j_invariant(0.0).is_err());
assert!(klein_j_invariant(-1.0).is_err());
}
#[test]
fn test_weierstrass_p_near_zero() {
let z = 0.01f64;
let v = weierstrass_p(z, 1.0, 0.0, 10).expect("should not fail");
assert!((v - 1.0 / (z * z)).abs() / (1.0 / (z * z)) < 0.01);
}
#[test]
fn test_weierstrass_p_zero_arg_error() {
assert!(weierstrass_p(0.0, 1.0, 0.0, 10).is_err());
}
#[test]
fn test_weierstrass_p_prime_near_zero() {
let z = 0.01f64;
let vp = weierstrass_p_prime(z, 1.0, 0.0, 10).expect("should not fail");
let expected = -2.0 / (z * z * z);
assert!((vp - expected).abs() / expected.abs() < 0.01);
}
#[test]
fn test_theta1_zero_at_origin() {
let v = lattice_theta1(0.0, 0.3).expect("should not fail");
assert!(v.abs() < 1e-14);
}
#[test]
fn test_theta3_greater_than_one_at_zero() {
let v = lattice_theta3(0.0, 0.3).expect("should not fail");
assert!(v > 1.0);
}
#[test]
fn test_theta4_at_zero() {
let v = lattice_theta4(0.0, 0.3).expect("should not fail");
assert!(v < 1.0);
}
#[test]
fn test_theta2_convergence() {
let v = lattice_theta2(0.0, 0.2).expect("should not fail");
assert!(v > 0.0 && v.is_finite());
}
#[test]
fn test_theta_invalid_nome() {
assert!(lattice_theta1(0.0, 1.0).is_err());
assert!(lattice_theta1(0.0, -0.1).is_err());
}
#[test]
fn test_divisor_power_sum() {
assert_relative_eq!(divisor_power_sum(1, 3), 1.0, epsilon = 1e-10);
assert_relative_eq!(divisor_power_sum(2, 3), 9.0, epsilon = 1e-10);
assert_relative_eq!(divisor_power_sum(3, 3), 28.0, epsilon = 1e-10);
assert_relative_eq!(divisor_power_sum(4, 3), 73.0, epsilon = 1e-10);
}
}