design.rs
11.2 kB · rust · 341 lines
1use crate::factor::mobius_sieve;2use crate::fft::fft;3use std::f64::consts::PI;45/// The ordinates of the first fourteen nontrivial zeros of the Riemann zeta function, the imaginary parts of the zeros on the critical line in ascending order.6pub const ZETA_ORDINATES: [f64; 14] = [7 14.134725141734693,8 21.022039638771556,9 25.01085758014569,10 30.424876125859512,11 32.93506158773919,12 37.58617815882567,13 40.9187190121475,14 43.327073280915,15 48.00515088116716,16 49.7738324776723,17 52.97032147771446,18 56.44624769706339,19 59.34704400260235,20 60.83177852460981,21];2223/// Returns the digits a bitmask names inside the base, ascending.24///25/// ```26/// assert_eq!(mrlynum::design::digits_of(0b1011, 10), vec![0, 1, 3]);27/// ```28pub fn digits_of(mask: u32, base: u64) -> Vec<u64> {29 (0..base).filter(|&d| mask & (1u32 << d) != 0).collect()30}3132/// Returns the elements of the digit design below the base raised to the depth, ascending: the whole numbers of at most that many base digits, every digit drawn from the set and the leading digit nonzero.33///34/// ```35/// assert_eq!(mrlynum::design::elements(3, &[0, 1], 3), vec![1, 3, 4, 9, 10, 12, 13]);36/// ```37pub fn elements(base: u64, digits: &[u64], depth: usize) -> Vec<u64> {38 let lead: Vec<u64> = digits.iter().copied().filter(|&d| d > 0).collect();39 if depth == 0 || lead.is_empty() {40 return Vec::new();41 }42 let mut out = lead.clone();43 let mut level = lead;44 for _ in 1..depth {45 let mut next = Vec::with_capacity(level.len() * digits.len());46 for value in &level {47 for digit in digits {48 next.push(value * base + digit);49 }50 }51 out.extend_from_slice(&next);52 level = next;53 }54 out55}5657/// Returns the count of elements the design holds at the depth, the length [`elements`] returns without building them.58///59/// ```60/// assert_eq!(mrlynum::design::size(&[0, 1], 20), 1048575);61/// ```62pub fn size(digits: &[u64], depth: usize) -> u128 {63 let lead = digits.iter().filter(|&&d| d > 0).count() as u128;64 let k = digits.len() as u128;65 if depth == 0 || lead == 0 {66 return 0;67 }68 let mut run = 1u128;69 let mut total = 0u128;70 for _ in 0..depth {71 total += lead * run;72 run *= k;73 }74 total75}7677/// Returns the Mobius value of every number by trial division over the primes below the square root of the largest.78///79/// ```80/// assert_eq!(mrlynum::design::mobius_of(&[1, 2, 3, 4, 5, 6]), vec![1, -1, -1, 0, -1, 1]);81/// ```82pub fn mobius_of(values: &[u64]) -> Vec<i8> {83 let top = values.iter().copied().max().unwrap_or(0);84 let root = (top as f64).sqrt() as usize + 2;85 let primes: Vec<u64> = crate::classics::primes(root)86 .into_iter()87 .map(|p| p as u64)88 .collect();89 values90 .iter()91 .map(|&value| {92 let mut rest = value;93 let mut sign = 1i8;94 for &p in &primes {95 if p * p > rest {96 break;97 }98 if rest % p == 0 {99 rest /= p;100 if rest % p == 0 {101 return 0;102 }103 sign = -sign;104 }105 }106 if rest > 1 {107 sign = -sign;108 }109 sign110 })111 .collect()112}113114/// Returns the running design Mobius meter, the partial sums of the Mobius values along the elements.115///116/// ```117/// assert_eq!(mrlynum::design::meter(&[1, -1, -1, 0, -1]), vec![1, 0, -1, -1, -2]);118/// ```119pub fn meter(mu: &[i8]) -> Vec<i64> {120 let mut total = 0i64;121 mu.iter()122 .map(|&value| {123 total += i64::from(value);124 total125 })126 .collect()127}128129/// Returns the log grid uniform over the span of the elements, from the log of the first to the log of the last.130pub fn log_grid(values: &[u64], samples: usize) -> Vec<f64> {131 if values.is_empty() || samples == 0 {132 return Vec::new();133 }134 let lo = (values[0] as f64).ln();135 let hi = (values[values.len() - 1] as f64).ln();136 let step = if samples > 1 {137 (hi - lo) / (samples - 1) as f64138 } else {139 0.0140 };141 (0..samples).map(|i| lo + step * i as f64).collect()142}143144/// Reads the running meter at every point of the log grid and divides by x to the exponent.145pub fn resample(values: &[u64], running: &[i64], exponent: f64, log_x: &[f64]) -> Vec<f64> {146 log_x147 .iter()148 .map(|&t| {149 let slot = values.partition_point(|&v| (v as f64).ln() <= t);150 let held = if slot == 0 {151 0.0152 } else {153 running[slot - 1] as f64154 };155 held / (exponent * t).exp()156 })157 .collect()158}159160/// Returns the density echo, the sum of mu(n) A_F(n)/n over the whole numbers up to each grid point divided by x to the exponent, sieving the Mobius values to the largest element.161pub fn echo_series(values: &[u64], log_x: &[f64], exponent: f64) -> Vec<f64> {162 let top = match values.last() {163 Some(&value) => value,164 None => return vec![0.0; log_x.len()],165 };166 let mu = mobius_sieve(top as usize);167 let mut out = Vec::with_capacity(log_x.len());168 let mut total = 0.0f64;169 let mut seen = 0u64;170 let mut at = 0usize;171 let mut n = 1u64;172 for &t in log_x {173 let mut bound = t.exp().floor() as u64;174 while bound < top && ((bound + 1) as f64).ln() <= t {175 bound += 1;176 }177 while bound > 0 && (bound as f64).ln() > t {178 bound -= 1;179 }180 while n <= bound {181 if at < values.len() && values[at] == n {182 seen += 1;183 at += 1;184 }185 total += f64::from(mu[n as usize]) * seen as f64 / n as f64;186 n += 1;187 }188 out.push(total / (exponent * t).exp());189 }190 out191}192193/// Returns the frequency axis and the power spectrum of the series: the mean removed, a Hann window laid on, a real transform taken, and bin j read as the ordinate 2 pi j over the log range.194pub fn spectrum(log_x: &[f64], series: &[f64]) -> (Vec<f64>, Vec<f64>) {195 let n = series.len();196 if n < 2 || !n.is_power_of_two() {197 return (Vec::new(), Vec::new());198 }199 let step = (log_x[n - 1] - log_x[0]) / (n - 1) as f64;200 let mean = series.iter().sum::<f64>() / n as f64;201 let mut re: Vec<f64> = series202 .iter()203 .enumerate()204 .map(|(i, &value)| {205 let window = 0.5 - 0.5 * (2.0 * PI * i as f64 / (n - 1) as f64).cos();206 (value - mean) * window207 })208 .collect();209 let mut im = vec![0.0f64; n];210 fft(&mut re, &mut im, false);211 let range = n as f64 * step;212 let bins = n / 2 + 1;213 let gamma = (0..bins).map(|j| 2.0 * PI * j as f64 / range).collect();214 let power = (0..bins).map(|j| re[j] * re[j] + im[j] * im[j]).collect();215 (gamma, power)216}217218/// Returns the running median of the power over a window of the given width, the window clamped at the ends.219pub fn median_floor(power: &[f64], width: usize) -> Vec<f64> {220 let n = power.len();221 if n == 0 || width == 0 {222 return vec![0.0; n];223 }224 let half = width / 2;225 let mut window = vec![0.0f64; width];226 (0..n)227 .map(|i| {228 for (slot, cell) in window.iter_mut().enumerate() {229 let at = (i + slot).saturating_sub(half).min(n - 1);230 *cell = power[at];231 }232 let mid = width / 2;233 let (_, value, _) =234 window.select_nth_unstable_by(mid, |a, b| a.partial_cmp(b).unwrap());235 *value236 })237 .collect()238}239240/// Returns the power over its local median floor, the score a peak is read against.241pub fn score(power: &[f64], width: usize) -> Vec<f64> {242 let floor = median_floor(power, width);243 power244 .iter()245 .zip(floor.iter())246 .map(|(&value, &base)| value / base.max(f64::MIN_POSITIVE))247 .collect()248}249250/// Returns the bins inside the band that rise above both neighbours and clear the score threshold, strongest first.251pub fn peaks(gamma: &[f64], score: &[f64], band: (f64, f64), threshold: f64) -> Vec<usize> {252 let mut found: Vec<usize> = (1..score.len().saturating_sub(1))253 .filter(|&i| {254 gamma[i] > band.0255 && gamma[i] < band.1256 && score[i] > score[i - 1]257 && score[i] > score[i + 1]258 && score[i] > threshold259 })260 .collect();261 found.sort_by(|&a, &b| score[b].partial_cmp(&score[a]).unwrap());262 found263}264265/// Returns the design's pole lattice below the top, the ordinates 2 pi j over log q of the poles its Dirichlet series carries.266///267/// ```268/// let lines = mrlynum::design::pole_lattice(3, 13.0);269/// assert_eq!(format!("{:.4} {:.4} {}", lines[0], lines[1], lines.len()), "5.7192 11.4384 2");270/// ```271pub fn pole_lattice(base: u64, top: f64) -> Vec<f64> {272 let step = 2.0 * PI / (base as f64).ln();273 let mut out = Vec::new();274 let mut j = 1;275 while step * (j as f64) < top {276 out.push(step * j as f64);277 j += 1;278 }279 out280}281282/// Returns the distance from the ordinate to the nearest entry of the list, infinite when the list is empty.283pub fn nearest(value: f64, list: &[f64]) -> f64 {284 list.iter()285 .map(|&entry| (entry - value).abs())286 .fold(f64::INFINITY, f64::min)287}288289/// Returns the root mean square of the upper half of the series, the size the echo and the meter are compared at.290pub fn upper_rms(series: &[f64]) -> f64 {291 let half = series.len() / 2;292 let tail = &series[half..];293 if tail.is_empty() {294 return 0.0;295 }296 (tail.iter().map(|value| value * value).sum::<f64>() / tail.len() as f64).sqrt()297}298299#[cfg(test)]300mod tests {301 use super::*;302 use crate::factor::mobius;303304 #[test]305 fn the_design_is_the_digit_strings_in_order() {306 assert_eq!(elements(10, &[0, 1], 2), vec![1, 10, 11]);307 assert_eq!(elements(3, &[1, 2], 2), vec![1, 2, 4, 5, 7, 8]);308 for depth in 1..=8 {309 let built = elements(3, &[0, 1], depth);310 assert_eq!(built.len() as u128, size(&[0, 1], depth));311 assert!(built.windows(2).all(|pair| pair[0] < pair[1]));312 }313 let full = elements(10, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 4);314 assert_eq!(full, (1..10000).collect::<Vec<u64>>());315 }316317 #[test]318 fn the_two_mobius_paths_agree() {319 let values = elements(3, &[0, 1], 12);320 let quick = mobius_of(&values);321 let slow: Vec<i8> = values.iter().map(|&v| mobius(v as usize)).collect();322 assert_eq!(quick, slow);323 }324325 #[test]326 fn the_frequency_axis_is_two_pi_over_the_log_range() {327 let values = elements(3, &[0, 1], 10);328 let running = meter(&mobius_of(&values));329 let log_x = log_grid(&values, 1024);330 let series = resample(&values, &running, 0.5 * 2f64.ln() / 3f64.ln(), &log_x);331 let (gamma, power) = spectrum(&log_x, &series);332 assert_eq!(gamma.len(), 513);333 assert_eq!(power.len(), 513);334 let step = (log_x[1023] - log_x[0]) / 1023.0;335 let range = 1024.0 * step;336 assert_eq!(gamma[0], 0.0);337 for j in [1usize, 7, 100, 512] {338 assert_eq!(gamma[j], 2.0 * PI * j as f64 / range);339 }340 }341}