use crate::error::{SpecialError, SpecialResult};
use crate::l_functions::{dirichlet_l, DirichletCharacter};
use std::f64::consts::PI;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum NumberField {
Rational,
Quadratic {
discriminant: i64,
},
Cyclotomic {
n: usize,
},
}
#[derive(Debug, Clone)]
pub struct DedekindConfig {
pub n_terms: usize,
pub use_euler_product: bool,
}
impl Default for DedekindConfig {
fn default() -> Self {
DedekindConfig {
n_terms: 1000,
use_euler_product: true,
}
}
}
pub fn riemann_zeta_partial(s: f64, n_terms: usize) -> f64 {
if s <= 1.0 {
return f64::NAN;
}
let n = n_terms as f64;
let mut sum = 0.0f64;
for k in 1..=n_terms {
sum += (k as f64).powf(-s);
}
let tail = n.powf(1.0 - s) / (s - 1.0) + 0.5 * n.powf(-s);
let b2_correction = (1.0 / 6.0) * 0.5 * s * n.powf(-s - 1.0);
let b4_correction =
(-1.0 / 30.0) / 24.0 * s * (s + 1.0) * (s + 2.0) * (s + 3.0) * n.powf(-s - 4.0);
sum + tail + b2_correction + b4_correction
}
pub fn dedekind_zeta_quadratic(
s: f64,
discriminant: i64,
config: &DedekindConfig,
) -> SpecialResult<f64> {
if s <= 1.0 {
return Err(SpecialError::DomainError(format!(
"Dedekind zeta requires s > 1, got s = {s}"
)));
}
if discriminant == 0 {
return Err(SpecialError::ValueError(
"Discriminant must be nonzero".to_string(),
));
}
let zeta_s = riemann_zeta_partial(s, config.n_terms);
let chi = DirichletCharacter::kronecker_symbol(discriminant);
let l_s = dirichlet_l(s, &chi, config.n_terms);
Ok(zeta_s * l_s)
}
fn gcd(a: usize, b: usize) -> usize {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
fn euler_phi(n: usize) -> usize {
if n == 0 {
return 0;
}
let mut result = n;
let mut m = n;
let mut p = 2usize;
while p * p <= m {
if m.is_multiple_of(p) {
while m.is_multiple_of(p) {
m /= p;
}
result -= result / p;
}
p += 1;
}
if m > 1 {
result -= result / m;
}
result
}
fn enumerate_real_characters(n: usize) -> Vec<DirichletCharacter> {
let mut chars = Vec::new();
chars.push(DirichletCharacter::principal(n));
let mut checked = std::collections::HashSet::new();
checked.insert(n);
for d in 1..=n {
if n.is_multiple_of(d) {
for &disc in &[d as i64, -(d as i64)] {
let chi = DirichletCharacter::kronecker_symbol(disc);
if chi.modulus == n && chi.is_real() && !chi.is_principal_char() {
let key: Vec<i64> = chi.values.clone();
if !chars.iter().any(|c: &DirichletCharacter| c.values == key) {
chars.push(chi);
}
}
}
}
}
chars
}
trait IsPrincipal {
fn is_principal_char(&self) -> bool;
}
impl IsPrincipal for DirichletCharacter {
fn is_principal_char(&self) -> bool {
self.values.iter().all(|&v| v == 0 || v == 1) && self.values.contains(&1) && {
let q = self.modulus;
self.values
.iter()
.enumerate()
.all(|(n, &v)| if gcd(n, q) == 1 { v == 1 } else { v == 0 })
}
}
}
pub fn dedekind_zeta_cyclotomic(s: f64, n: usize, config: &DedekindConfig) -> SpecialResult<f64> {
if s <= 1.0 {
return Err(SpecialError::DomainError(format!(
"Dedekind zeta requires s > 1, got s = {s}"
)));
}
if n == 0 {
return Err(SpecialError::ValueError(
"Cyclotomic level n must be positive".to_string(),
));
}
if n == 1 {
return Ok(riemann_zeta_partial(s, config.n_terms));
}
let chars = enumerate_real_characters(n);
let mut product = 1.0f64;
for chi in &chars {
let l_val = dirichlet_l(s, chi, config.n_terms);
if l_val.abs() < 1e-15 {
return Err(SpecialError::ComputationError(
"L-function value too small, potential precision issue".to_string(),
));
}
product *= l_val;
}
Ok(product)
}
pub fn dedekind_zeta(field: &NumberField, s: f64, config: &DedekindConfig) -> SpecialResult<f64> {
if s <= 1.0 {
return Err(SpecialError::DomainError(format!(
"Dedekind zeta requires s > 1, got s = {s}"
)));
}
match field {
NumberField::Rational => Ok(riemann_zeta_partial(s, config.n_terms)),
NumberField::Quadratic { discriminant } => {
dedekind_zeta_quadratic(s, *discriminant, config)
}
NumberField::Cyclotomic { n } => dedekind_zeta_cyclotomic(s, *n, config),
}
}
pub fn class_number_formula_check(discriminant: i64) -> f64 {
if discriminant >= 0 {
return f64::NAN;
}
let d_abs = discriminant.unsigned_abs() as f64;
let chi = DirichletCharacter::kronecker_symbol(discriminant);
let l1 = dirichlet_l(1.0 - 1e-8, &chi, 50_000);
let w = match discriminant {
-4 => 4.0,
-3 => 6.0,
_ => 2.0,
};
d_abs.sqrt() * w / (2.0 * PI) * l1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_riemann_zeta_at_2() {
let val = riemann_zeta_partial(2.0, 1000);
let expected = PI * PI / 6.0;
assert!(
(val - expected).abs() < 1e-3,
"ζ(2) ≈ {val}, expected ≈ {expected}"
);
}
#[test]
fn test_dedekind_zeta_rational() {
let config = DedekindConfig::default();
let val = dedekind_zeta(&NumberField::Rational, 2.0, &config).expect("ok");
let expected = PI * PI / 6.0;
assert!(
(val - expected).abs() < 1e-3,
"ζ_Q(2) ≈ {val}, expected ≈ {expected}"
);
}
#[test]
fn test_dedekind_zeta_quadratic_gaussian_integers() {
let config = DedekindConfig::default();
let val_qi =
dedekind_zeta(&NumberField::Quadratic { discriminant: -4 }, 2.0, &config).expect("ok");
assert!(
val_qi > 0.0,
"ζ_{{Q(i)}}(2) should be positive, got {val_qi}"
);
assert!(val_qi > 1.0, "ζ_{{Q(i)}}(2) should be > 1, got {val_qi}");
assert!(val_qi < 2.0, "ζ_{{Q(i)}}(2) should be < 2, got {val_qi}");
}
#[test]
fn test_class_number_gaussian_integers() {
let h = class_number_formula_check(-4);
assert!(
(h - 1.0).abs() < 0.1,
"Class number of Q(i) ≈ {h}, expected ≈ 1"
);
}
#[test]
fn test_class_number_eisenstein_integers() {
let h = class_number_formula_check(-3);
assert!(
(h - 1.0).abs() < 0.2,
"Class number of Q(√-3) ≈ {h}, expected ≈ 1"
);
}
#[test]
fn test_dedekind_zeta_domain_error() {
let config = DedekindConfig::default();
let result = dedekind_zeta(&NumberField::Rational, 0.5, &config);
assert!(result.is_err());
}
#[test]
fn test_euler_phi() {
assert_eq!(euler_phi(1), 1);
assert_eq!(euler_phi(4), 2);
assert_eq!(euler_phi(6), 2);
assert_eq!(euler_phi(5), 4);
assert_eq!(euler_phi(12), 4);
}
#[test]
fn test_dedekind_zeta_cyclotomic_q1() {
let config = DedekindConfig::default();
let val = dedekind_zeta(&NumberField::Cyclotomic { n: 1 }, 2.0, &config).expect("ok");
let expected = PI * PI / 6.0;
assert!(
(val - expected).abs() < 1e-3,
"ζ_{{Q(ζ_1)}}(2) ≈ {val}, expected {expected}"
);
}
}