use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct DirichletCharacter {
pub modulus: usize,
pub values: Vec<i64>,
}
impl DirichletCharacter {
pub fn principal(modulus: usize) -> Self {
let values = (0..modulus)
.map(|n| if gcd(n, modulus) == 1 { 1i64 } else { 0i64 })
.collect();
DirichletCharacter { modulus, values }
}
pub fn kronecker_symbol(d: i64) -> Self {
let modulus = d.unsigned_abs() as usize;
if modulus == 0 {
return DirichletCharacter {
modulus: 1,
values: vec![1],
};
}
let values = (0..modulus)
.map(|n| {
if gcd(n, modulus) != 1 {
0i64
} else {
kronecker(d, n as i64)
}
})
.collect();
DirichletCharacter { modulus, values }
}
pub fn eval(&self, n: i64) -> i64 {
if self.modulus == 0 {
return 0;
}
let idx = n.rem_euclid(self.modulus as i64) as usize;
self.values[idx]
}
pub fn is_primitive(&self) -> bool {
self.conductor() == self.modulus
}
pub fn conductor(&self) -> usize {
let q = self.modulus;
if q == 1 {
return 1;
}
let mut divs: Vec<usize> = (1..=q).filter(|&d| q.is_multiple_of(d)).collect();
divs.sort_unstable();
for d in divs {
if d == q {
return q;
}
if self.is_induced_by(d) {
return d;
}
}
q
}
fn is_induced_by(&self, d: usize) -> bool {
let q = self.modulus;
if !q.is_multiple_of(d) {
return false;
}
for a in 0..q {
if gcd(a, q) != 1 {
continue;
}
let chi_a = self.values[a];
let mut b = a % d;
while b < q {
if gcd(b, q) == 1 && self.values[b] != chi_a {
return false;
}
b += d;
}
}
true
}
pub fn is_real(&self) -> bool {
self.values.iter().all(|&v| v == -1 || v == 0 || v == 1)
}
}
pub fn gcd(a: usize, b: usize) -> usize {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
pub fn jacobi_symbol(mut a: i64, mut n: i64) -> i64 {
debug_assert!(n > 0 && n % 2 == 1);
let mut result = 1i64;
a = a.rem_euclid(n);
while a != 0 {
while a % 2 == 0 {
a /= 2;
let r = n % 8;
if r == 3 || r == 5 {
result = -result;
}
}
std::mem::swap(&mut a, &mut n);
if a % 4 == 3 && n % 4 == 3 {
result = -result;
}
a = a.rem_euclid(n);
}
if n == 1 {
result
} else {
0
}
}
pub fn kronecker(a: i64, n: i64) -> i64 {
if n == 0 {
return if a.abs() == 1 { 1 } else { 0 };
}
if n == 1 {
return 1;
}
if n == -1 {
return if a < 0 { -1 } else { 1 };
}
let mut n = n;
let mut result = 1i64;
if n < 0 {
n = -n;
if a < 0 {
result = -result;
}
}
let v2 = n.trailing_zeros() as i64;
if v2 > 0 {
n >>= v2;
let a8 = a.rem_euclid(8);
let k2 = if a8 == 1 || a8 == 7 { 1i64 } else { -1i64 };
if v2 % 2 != 0 {
result *= k2;
}
}
if n == 1 {
return result;
}
result * jacobi_symbol(a, n)
}
pub fn dirichlet_l(s: f64, chi: &DirichletCharacter, n_terms: usize) -> f64 {
let q = chi.modulus;
if q == 0 {
return 0.0;
}
let full_periods = n_terms / q;
let remainder = n_terms % q;
let mut sum = 0.0f64;
for period in 0..full_periods {
let base = period * q;
for r in 1..=q {
let n = base + r;
let chi_n = chi.eval(n as i64);
if chi_n != 0 {
sum += chi_n as f64 / (n as f64).powf(s);
}
}
}
let base = full_periods * q;
for r in 1..=remainder {
let n = base + r;
let chi_n = chi.eval(n as i64);
if chi_n != 0 {
sum += chi_n as f64 / (n as f64).powf(s);
}
}
sum
}
pub fn l_function_at_1(chi: &DirichletCharacter) -> Option<f64> {
let q = chi.modulus;
let sum_chi: i64 = (0..q).map(|n| chi.eval(n as i64)).sum();
if sum_chi > 0 && q > 1 {
return None; }
if q == 1 {
return None;
}
let chi_neg1 = chi.eval(-1);
if chi.is_real() && chi_neg1 == 1 {
let q_f = q as f64;
let sum: f64 = (1..q)
.map(|a| {
let chi_a = chi.eval(a as i64) as f64;
let sin_val = (PI * a as f64 / q_f).sin().abs();
if sin_val > 1e-15 {
chi_a * sin_val.ln()
} else {
0.0
}
})
.sum();
Some(-sum / q_f)
} else if chi.is_real() && chi_neg1 == -1 {
let q_f = q as f64;
let tau_im: f64 = (1..q)
.map(|a| chi.eval(a as i64) as f64 * (2.0 * PI * a as f64 / q_f).sin())
.sum();
let s: f64 = (1..q).map(|a| a as f64 * chi.eval(a as i64) as f64).sum();
let l1 = -PI * s / (q_f * tau_im);
Some(l1)
} else {
Some(dirichlet_l(1.0, chi, 100000))
}
}
pub fn generalized_bernoulli(k: usize, chi: &DirichletCharacter) -> f64 {
let q = chi.modulus as f64;
let q_pow = q.powi(k as i32 - 1);
let sum: f64 = (1..=chi.modulus)
.map(|a| {
let chi_a = chi.eval(a as i64) as f64;
let x = a as f64 / q;
chi_a * bernoulli_poly(k, x)
})
.sum();
q_pow * sum
}
pub fn bernoulli_poly(k: usize, x: f64) -> f64 {
let berns = bernoulli_numbers(k);
let mut result = 0.0f64;
let mut binom = 1.0f64;
for j in 0..=k {
result += binom * berns[j] * x.powi((k - j) as i32);
if j < k {
binom *= (k - j) as f64 / (j + 1) as f64;
}
}
result
}
pub fn bernoulli_numbers(n: usize) -> Vec<f64> {
let mut b = vec![0.0f64; n + 1];
b[0] = 1.0;
if n == 0 {
return b;
}
b[1] = -0.5;
for m in 2..=n {
let mut s = 0.0f64;
let mut binom = 1.0f64;
for k in 0..m {
s += binom * b[k];
binom *= (m + 1 - k) as f64 / (k + 1) as f64;
}
b[m] = -s / (m + 1) as f64;
}
b
}
pub fn l_function_complete(s: f64, chi: &DirichletCharacter) -> f64 {
let q = chi.modulus as f64;
let a = if chi.eval(-1) == 1 { 0.0f64 } else { 1.0f64 };
let gamma_arg = (s + a) / 2.0;
let gamma_factor = gamma_function(gamma_arg);
let l_val = dirichlet_l(s, chi, 50000);
let completion = (q / PI).powf(s / 2.0);
completion * gamma_factor * l_val
}
fn gamma_function(x: f64) -> f64 {
if x <= 0.0 {
return f64::INFINITY;
}
if x < 0.5 {
return PI / ((PI * x).sin() * gamma_function(1.0 - x));
}
const G: f64 = 7.0;
const C: [f64; 9] = [
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7,
];
let x = x - 1.0;
let mut s = C[0];
for (i, &c) in C[1..].iter().enumerate() {
s += c / (x + (i + 1) as f64);
}
let t = x + G + 0.5;
(2.0 * PI).sqrt() * t.powf(x + 0.5) * (-t).exp() * s
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_gcd() {
assert_eq!(gcd(12, 8), 4);
assert_eq!(gcd(7, 5), 1);
assert_eq!(gcd(0, 5), 5);
}
#[test]
fn test_kronecker() {
assert_eq!(kronecker(5, 3), -1);
assert_eq!(kronecker(1, 5), 1);
assert_eq!(kronecker(1, 7), 1);
assert_eq!(kronecker(-1, 2), 1); }
#[test]
fn test_principal_character() {
let chi = DirichletCharacter::principal(4);
assert_eq!(chi.eval(1), 1);
assert_eq!(chi.eval(2), 0);
assert_eq!(chi.eval(3), 1);
assert_eq!(chi.eval(4), 0);
}
#[test]
fn test_kronecker_character_neg4() {
let chi = DirichletCharacter::kronecker_symbol(-4);
assert_eq!(chi.eval(1), 1);
assert_eq!(chi.eval(3), -1);
assert_eq!(chi.eval(2), 0);
}
#[test]
fn test_dirichlet_l_principal_mod4() {
let chi = DirichletCharacter::principal(4);
let l2 = dirichlet_l(2.0, &chi, 100000);
let expected = std::f64::consts::PI * std::f64::consts::PI / 8.0;
assert_relative_eq!(l2, expected, epsilon = 1e-3);
}
#[test]
fn test_l_function_at_1_legendre_neg4() {
let chi = DirichletCharacter::kronecker_symbol(-4);
let l1 = l_function_at_1(&chi).expect("should compute L(1,chi)");
let expected = std::f64::consts::PI / 4.0;
assert_relative_eq!(l1, expected, epsilon = 1e-6);
}
#[test]
fn test_bernoulli_numbers() {
let b = bernoulli_numbers(6);
assert_relative_eq!(b[0], 1.0, epsilon = 1e-12);
assert_relative_eq!(b[1], -0.5, epsilon = 1e-12);
assert_relative_eq!(b[2], 1.0 / 6.0, epsilon = 1e-12);
assert_relative_eq!(b[3], 0.0, epsilon = 1e-12);
assert_relative_eq!(b[4], -1.0 / 30.0, epsilon = 1e-12);
}
#[test]
fn test_bernoulli_poly_b1() {
let b1 = bernoulli_poly(1, 0.3);
assert_relative_eq!(b1, 0.3 - 0.5, epsilon = 1e-12);
}
#[test]
fn test_generalized_bernoulli_chi0() {
let chi = DirichletCharacter::principal(1);
let b2 = generalized_bernoulli(2, &chi);
assert!(b2.is_finite());
}
#[test]
fn test_conductor_principal() {
let chi = DirichletCharacter::principal(6);
let cond = chi.conductor();
assert!(6 % cond == 0);
}
#[test]
fn test_character_real() {
let chi = DirichletCharacter::kronecker_symbol(-4);
assert!(chi.is_real());
}
}