series.rs
10.4 kB · rust · 332 lines
1use crate::classics::primes;2use crate::factor::mobius_sieve;3use std::f64::consts::PI;45/// The Basel constant, pi squared over six, the value zeta takes at two.6pub const BASEL: f64 = PI * PI / 6.0;78/// The visible density, six over pi squared, the share of lattice pairs that are coprime.9pub const VISIBLE: f64 = 6.0 / (PI * PI);1011/// The Catalan constant, the value the Dirichlet beta function takes at two.12pub const CATALAN: f64 = 0.915_965_594_177_219;1314/// The Apery constant, the value zeta takes at three.15pub const APERY: f64 = 1.202_056_903_159_594;1617/// The Euler constant, the limit of the harmonic sum less the logarithm.18pub const EULER: f64 = 0.577_215_664_901_532_9;1920/// Returns the logarithmic integral of a positive x by the Ramanujan series, the smooth count of the primes below x.21///22/// ```23/// assert!((mrlynum::series::li(1_000_000.0) - 78_627.549).abs() < 1e-3);24/// ```25pub fn li(x: f64) -> f64 {26 let log = x.ln();27 let mut sum = 0.0f64;28 let mut term = 1.0f64;29 let mut odds = 0.0f64;30 for n in 1..200u32 {31 term *= log / n as f64;32 if n > 1 {33 term /= 2.0;34 }35 if !n.is_multiple_of(2) {36 odds += 1.0 / n as f64;37 }38 let piece = term * odds;39 sum += if n.is_multiple_of(2) { -piece } else { piece };40 if piece.abs() < 1e-17 * sum.abs() {41 break;42 }43 }44 EULER + log.abs().ln() + x.sqrt() * sum45}4647/// Returns the partial harmonic sum, the reciprocals of one through the term count.48pub fn harmonic(terms: usize) -> f64 {49 (1..=terms).map(|k| 1.0 / k as f64).sum()50}5152/// Returns the zeta value above one: the partial sum closed by its Euler-Maclaurin tail.53///54/// Panics at an s of one or below, where the sum does not converge.55pub fn zeta(s: f64, terms: usize) -> f64 {56 assert!(s > 1.0, "zeta needs an s above one");57 let mut sum = 0.0;58 for k in 1..=terms {59 sum += (k as f64).powf(-s);60 }61 let n = terms as f64;62 sum + n.powf(1.0 - s) / (s - 1.0) - n.powf(-s) / 2.0 + s * n.powf(-s - 1.0) / 12.063}6465/// Returns the Euler product of zeta, one over one minus p to the minus s over the primes up to the limit.66pub fn euler_product(s: f64, limit: usize) -> f64 {67 primes(limit)68 .iter()69 .map(|&p| 1.0 / (1.0 - (p as f64).powf(-s)))70 .product()71}7273/// Returns the Dirichlet beta value, the alternating odd-denominator sum averaged over its last two partial sums.74pub fn beta(s: f64, terms: usize) -> f64 {75 let mut sum = 0.0;76 let mut previous = 0.0;77 for k in 0..terms {78 previous = sum;79 let term = ((2 * k + 1) as f64).powf(-s);80 sum += if k.is_multiple_of(2) { term } else { -term };81 }82 0.5 * (previous + sum)83}8485/// Returns the Dirichlet lambda value, one minus two to the minus s times zeta.86pub fn lambda(s: f64, terms: usize) -> f64 {87 (1.0 - 2f64.powf(-s)) * zeta(s, terms)88}8990/// Returns the mod-eight rhythm of the number, the discriminant minus-eight character: one on one and three, minus one on five and seven, zero on the evens.91///92/// ```93/// assert_eq!(mrlynum::series::chi8(3), 1);94/// assert_eq!(mrlynum::series::chi8(5), -1);95/// ```96pub fn chi8(number: usize) -> i8 {97 [0, 1, 0, 1, 0, -1, 0, -1][number % 8]98}99100/// Returns the mod-four rhythm of the number: zero, one, zero, minus one.101///102/// ```103/// assert_eq!(mrlynum::series::chi4(7), -1);104/// ```105pub fn chi4(number: usize) -> i8 {106 [0, 1, 0, -1][number % 4]107}108109/// Returns the mod-three rhythm of the number: zero, one, minus one.110pub fn chi3(number: usize) -> i8 {111 [0, 1, -1][number % 3]112}113114/// Returns the L-series partial sum with a periodic rhythm painted on the terms.115pub fn dirichlet(s: f64, rhythm: &[i8], terms: usize) -> f64 {116 if rhythm.is_empty() {117 return 0.0;118 }119 let mut sum = 0.0;120 for n in 1..=terms {121 let paint = rhythm[n % rhythm.len()];122 if paint != 0 {123 sum += f64::from(paint) * (n as f64).powf(-s);124 }125 }126 sum127}128129/// Counts the lattice points of the dimension-cube of the limit whose coordinates share no divisor, by Mobius inversion.130///131/// Panics at a zero dimension, and wraps once the limit to the dimension passes a signed hundred and twenty-eight bits.132pub fn visible(limit: usize, dimension: u32) -> u128 {133 assert!(dimension > 0, "visible needs a dimension above zero");134 let mu = mobius_sieve(limit);135 let mut total: i128 = 0;136 for (k, &value) in mu.iter().enumerate().skip(1) {137 if value == 0 {138 continue;139 }140 let block = (limit / k) as i128;141 total += i128::from(value) * block.pow(dimension);142 }143 total as u128144}145146/// Returns the Wallis product of one minus one over the odd squares, walking to pi over four.147pub fn wallis(factors: usize) -> f64 {148 let mut out = 1.0;149 for n in 1..=factors {150 let odd = (2 * n + 1) as f64;151 out *= 1.0 - 1.0 / (odd * odd);152 }153 out154}155156fn reduce(num: i128, den: i128) -> (i128, i128) {157 let (mut a, mut b) = (num.abs(), den.abs());158 while b != 0 {159 (a, b) = (b, a % b);160 }161 let sign = if den < 0 { -1 } else { 1 };162 if a == 0 {163 return (0, 1);164 }165 (sign * num / a, sign * den / a)166}167168fn binomial(n: usize, k: usize) -> i128 {169 let mut out: i128 = 1;170 for i in 0..k {171 out = out * (n - i) as i128 / (i + 1) as i128;172 }173 out174}175176/// Builds the first Bernoulli numbers as exact reduced fractions on the minus one half convention.177///178/// Panics past a count of thirty-two, where the exact fractions overflow a signed hundred and twenty-eight bits.179pub fn bernoulli(count: usize) -> Vec<(i128, i128)> {180 assert!(count <= 32, "the exact fractions overflow past thirty-two");181 let mut out: Vec<(i128, i128)> = Vec::with_capacity(count);182 for m in 0..count {183 if m == 0 {184 out.push((1, 1));185 continue;186 }187 let mut sum = (0i128, 1i128);188 for (j, &(num, den)) in out.iter().enumerate() {189 let weight = binomial(m + 1, j) * num;190 sum = reduce(sum.0 * den + weight * sum.1, sum.1 * den);191 }192 out.push(reduce(-sum.0, sum.1 * (m + 1) as i128));193 }194 out195}196197#[cfg(test)]198mod tests {199 use super::*;200 use crate::lattice::coprime_pairs;201202 #[test]203 fn zeta_meets_the_basel_the_apery_and_the_quartic_sum() {204 assert!((zeta(2.0, 10_000) - BASEL).abs() < 1e-9);205 assert!((zeta(3.0, 10_000) - APERY).abs() < 1e-12);206 assert!((zeta(4.0, 10_000) - PI.powi(4) / 90.0).abs() < 1e-9);207 }208209 #[test]210 fn the_euler_product_meets_the_basel_and_the_apery_sum() {211 assert!((euler_product(2.0, 100_000) - BASEL).abs() < 1e-5);212 assert!((euler_product(3.0, 100_000) - APERY).abs() < 1e-6);213 }214215 #[test]216 fn beta_walks_to_catalan_and_to_a_quarter_turn() {217 assert!((beta(2.0, 1_000_000) - CATALAN).abs() < 1e-9);218 assert!((beta(1.0, 1_000_000) - PI / 4.0).abs() < 1e-6);219 }220221 #[test]222 fn half_lambda_is_the_grid_fluctuation_constant() {223 assert!((0.5 * lambda(4.0, 10_000) - PI.powi(4) / 192.0).abs() < 1e-9);224 }225226 #[test]227 fn the_mod_four_rhythm_paints_the_beta_series() {228 let painted = dirichlet(2.0, &[0, 1, 0, -1], 1_000_000);229 assert!((painted - beta(2.0, 1_000_000)).abs() < 1e-9);230 }231232 #[test]233 fn the_mod_three_rhythm_paints_the_l_series() {234 let painted = dirichlet(1.0, &[0, 1, -1], 1_000_000);235 assert!((painted - PI / (3.0 * 3f64.sqrt())).abs() < 1e-5);236 }237238 #[test]239 fn visible_counts_the_coprime_pairs_of_a_window() {240 for n in 1..=2_000 {241 assert_eq!(visible(n, 2), u128::from(coprime_pairs(n)), "window {n}");242 }243 }244245 #[test]246 fn the_visible_density_is_one_over_zeta() {247 let flat = visible(10_000, 2) as f64 / 1e8;248 assert!((flat - VISIBLE).abs() < 1e-3);249 let cube = visible(1_000, 3) as f64 / 1e9;250 assert!((cube - 1.0 / zeta(3.0, 100_000)).abs() < 1e-2);251 }252253 #[test]254 fn the_harmonic_walk_leaves_the_euler_mascheroni_gap() {255 let gap = harmonic(1_000_000) - 1_000_000f64.ln();256 assert!((gap - 0.577_215_664_9).abs() < 1e-5);257 }258259 #[test]260 fn bernoulli_pins_the_known_fractions() {261 let list = bernoulli(32);262 assert_eq!(list[0], (1, 1));263 assert_eq!(list[1], (-1, 2));264 assert_eq!(list[2], (1, 6));265 assert_eq!(list[3], (0, 1));266 assert_eq!(list[4], (-1, 30));267 assert_eq!(list[12], (-691, 2730));268 }269270 #[test]271 fn every_odd_bernoulli_past_the_first_is_zero() {272 for (index, &(num, den)) in bernoulli(32).iter().enumerate().skip(3) {273 if !index.is_multiple_of(2) {274 assert_eq!((num, den), (0, 1), "index {index}");275 }276 }277 }278279 #[test]280 #[should_panic(expected = "visible needs a dimension above zero")]281 fn visible_refuses_a_zero_dimension() {282 let _ = visible(10, 0);283 }284285 #[test]286 #[should_panic(expected = "the exact fractions overflow past thirty-two")]287 fn bernoulli_refuses_a_count_past_thirty_two() {288 let _ = bernoulli(33);289 }290291 #[test]292 #[should_panic(expected = "zeta needs an s above one")]293 fn zeta_refuses_an_s_of_one() {294 let _ = zeta(1.0, 10);295 }296297 #[test]298 fn wallis_walks_to_a_quarter_turn() {299 assert!((wallis(1_000_000) - PI / 4.0).abs() < 1e-6);300 }301302 #[test]303 fn the_rhythms_repeat_over_their_full_period() {304 let four: Vec<i8> = (0..8).map(chi4).collect();305 assert_eq!(four, vec![0, 1, 0, -1, 0, 1, 0, -1]);306 let three: Vec<i8> = (0..6).map(chi3).collect();307 assert_eq!(three, vec![0, 1, -1, 0, 1, -1]);308 let eight: Vec<i8> = (0..16).map(chi8).collect();309 assert_eq!(310 eight,311 vec![0, 1, 0, 1, 0, -1, 0, -1, 0, 1, 0, 1, 0, -1, 0, -1]312 );313 }314315 #[test]316 fn the_mod_eight_rhythm_multiplies_across_the_odd_numbers() {317 for a in (1..=99usize).step_by(2) {318 for b in (1..=99usize).step_by(2) {319 assert_eq!(chi8(a) * chi8(b), chi8(a * b), "{a} {b}");320 }321 }322 }323324 #[test]325 fn li_pins_the_smooth_prime_counts() {326 assert!((li(2.0) - 1.045_163_780_1).abs() < 1e-9);327 assert!((li(1_000.0) - 177.609_657_990_2).abs() < 1e-8);328 assert!((li(10_000.0) - 1_246.137_215_9).abs() < 1e-6);329 assert!((li(100_000.0) - 9_629.809_001_1).abs() < 1e-6);330 assert!((li(1_000_000.0) - 78_627.549_159_5).abs() < 1e-6);331 }332}