zeta.rs

3.8 kB · rust · 112 lines

1use crate::Fault;2use mrlynum::zeta::{self, Line, JOIN};3use wasm_bindgen::prelude::*;45const REACH: f64 = 250.0;6const POINTS: usize = 20_000;7const ZEROS: usize = 100;8const STAIR: usize = 1_000;9const SAMPLES: usize = 2_000;1011fn checked(t: f64) -> Result<f64, Fault> {12    if !(0.0..=REACH).contains(&t) {13        return Err(Fault::new(format!("the line runs from t = 0 to {REACH}.")));14    }15    Ok(t)16}1718fn stones(x: f64, gammas: &[f64]) -> Result<(), Fault> {19    if !(2.0..=STAIR as f64).contains(&x) {20        return Err(Fault::new(format!("the staircase runs from 2 to {STAIR}.")));21    }22    if gammas.len() > ZEROS {23        return Err(Fault::new(format!(24            "the formula takes at most {ZEROS} zeros."25        )));26    }27    Ok(())28}2930/// Walks the critical line from one t to another in the given count of steps: for every point t, the real and the imaginary part of zeta at one half plus i t, and Z(t), as flat quadruples.31#[wasm_bindgen]32pub fn zeta_line(t0: f64, t1: f64, steps: usize) -> Result<Vec<f64>, Fault> {33    checked(t0)?;34    checked(t1)?;35    if steps == 0 || steps > POINTS {36        return Err(Fault::new(format!("the walk takes 1 to {POINTS} steps.")));37    }38    let line = Line::new();39    let mut out = Vec::with_capacity(4 * (steps + 1));40    for k in 0..=steps {41        let t = t0 + (t1 - t0) * k as f64 / steps as f64;42        let (value, z) = line.point(t);43        out.extend([t, value.re, value.im, z]);44    }45    Ok(out)46}4748/// Reads one point of the critical line: the real and the imaginary part of zeta, Z(t) and theta(t).49#[wasm_bindgen]50pub fn zeta_at(t: f64) -> Result<Vec<f64>, Fault> {51    let line = Line::new();52    let (value, z) = line.point(checked(t)?);53    Ok(vec![value.re, value.im, z, line.theta(t)])54}5556/// Returns the first zeros of zeta on the critical line, at most a hundred: sign changes of Z between Gram points, refined by bisection to a billionth.57#[wasm_bindgen]58pub fn zeta_zeros(count: usize) -> Result<Vec<f64>, Fault> {59    if count > ZEROS {60        return Err(Fault::new(format!("the list holds {ZEROS} zeros.")));61    }62    Ok(Line::new().zeros(count))63}6465/// Counts the zeros on the critical line below t.66#[wasm_bindgen]67pub fn zeta_count(t: f64) -> Result<u32, Fault> {68    Ok(Line::new().count(checked(t)?) as u32)69}7071/// Returns the join where Riemann-Siegel takes over from Euler-Maclaurin, and the largest gap between the two up to the given t on a grid of the given steps.72#[wasm_bindgen]73pub fn zeta_seam(t1: f64, steps: usize) -> Result<Vec<f64>, Fault> {74    if steps == 0 || steps > POINTS {75        return Err(Fault::new(format!("the seam takes 1 to {POINTS} steps.")));76    }77    Ok(vec![78        JOIN,79        Line::new().seam(JOIN, checked(t1)?.max(JOIN), steps),80    ])81}8283/// Returns the Chebyshev staircase psi at every whole number from one to x, at most a thousand.84#[wasm_bindgen]85pub fn psi_stair(x: usize) -> Result<Vec<f64>, Fault> {86    stones(x as f64, &[])?;87    Ok(zeta::psi_stair(x))88}8990/// Samples the explicit formula over the given zero ordinates at evenly spaced points from two to x, as flat pairs of the point and the value.91#[wasm_bindgen]92pub fn psi_formula(x: f64, gammas: &[f64], samples: usize) -> Result<Vec<f64>, Fault> {93    stones(x, gammas)?;94    if !(2..=SAMPLES).contains(&samples) {95        return Err(Fault::new(format!(96            "the formula takes 2 to {SAMPLES} samples."97        )));98    }99    let mut out = Vec::with_capacity(2 * samples);100    for k in 0..samples {101        let u = 2.0 + (x - 2.0) * k as f64 / (samples - 1) as f64;102        out.extend([u, zeta::psi_formula(u, gammas)]);103    }104    Ok(out)105}106107/// Returns psi(x) less the explicit formula over the given zeros at x.108#[wasm_bindgen]109pub fn psi_gap(x: usize, gammas: &[f64]) -> Result<f64, Fault> {110    stones(x as f64, gammas)?;111    Ok(zeta::psi_stair(x)[x - 1] - zeta::psi_formula(x as f64, gammas))112}