wiki-mahler-measure.rs

5.1 kB · rust · 175 lines

1use figures::{ink, save, Board};2use mrlyrs::core::error::Result;3use std::f64::consts::TAU;45const LEHMER: [f64; 11] = [1.0, 1.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0];6const SALEM: f64 = 1.176_280_818_259_917_5;7const SAMPLES: usize = 4096;8const FINE: usize = 1 << 16;9const STRETCH: f64 = 0.34;10const FLOOR: f64 = 2.0;1112type Z = (f64, f64);1314fn mul(a: Z, b: Z) -> Z {15    (a.0 * b.0 - a.1 * b.1, a.0 * b.1 + a.1 * b.0)16}1718fn div(a: Z, b: Z) -> Z {19    let d = b.0 * b.0 + b.1 * b.1;20    ((a.0 * b.0 + a.1 * b.1) / d, (a.1 * b.0 - a.0 * b.1) / d)21}2223fn norm(z: Z) -> f64 {24    z.0.hypot(z.1)25}2627fn eval(z: Z) -> Z {28    LEHMER.iter().rev().fold((0.0, 0.0), |acc, c| {29        let m = mul(acc, z);30        (m.0 + c, m.1)31    })32}3334fn height(t: f64) -> f64 {35    norm(eval((t.cos(), t.sin()))).ln()36}3738fn roots() -> Vec<Z> {39    let degree = LEHMER.len() - 1;40    let seed = (0.4, 0.9);41    let mut zs: Vec<Z> = Vec::with_capacity(degree);42    let mut z = (1.0, 0.0);43    for _ in 0..degree {44        zs.push(z);45        z = mul(z, seed);46    }47    for _ in 0..500 {48        for k in 0..degree {49            let mut den = (1.0, 0.0);50            for j in 0..degree {51                if j != k {52                    den = mul(den, (zs[k].0 - zs[j].0, zs[k].1 - zs[j].1));53                }54            }55            let step = div(eval(zs[k]), den);56            zs[k] = (zs[k].0 - step.0, zs[k].1 - step.1);57        }58    }59    zs60}6162fn squeeze(v: f64) -> f64 {63    if v >= 0.0 {64        v65    } else {66        -FLOOR * (1.0 - (v / FLOOR).exp())67    }68}6970fn main() -> Result<()> {71    let zs = roots();72    let on: Vec<Z> = zs73        .iter()74        .copied()75        .filter(|z| (norm(*z) - 1.0).abs() < 1e-9)76        .collect();77    let off: Vec<Z> = zs78        .iter()79        .copied()80        .filter(|z| (norm(*z) - 1.0).abs() >= 1e-9)81        .collect();82    assert_eq!(on.len(), 8);83    assert_eq!(off.len(), 2);84    let outside: f64 = off.iter().map(|z| norm(*z).max(1.0)).product();85    assert!((outside - SALEM).abs() < 1e-12);86    assert!(off.iter().all(|z| z.1.abs() < 1e-12 && z.0 > 0.0));87    let mean = (0..FINE)88        .map(|i| height((i as f64 + 0.5) / FINE as f64 * TAU))89        .sum::<f64>()90        / FINE as f64;91    assert!((mean - SALEM.ln()).abs() < 1e-4);9293    let reach: Vec<f64> = (0..SAMPLES)94        .map(|i| 1.0 + STRETCH * squeeze(height(i as f64 / SAMPLES as f64 * TAU)))95        .collect();96    let mut angles: Vec<f64> = (0..SAMPLES)97        .map(|i| i as f64 / SAMPLES as f64 * TAU)98        .collect();99    angles.extend(on.iter().map(|z| z.1.atan2(z.0).rem_euclid(TAU)));100    angles.sort_by(|a, b| a.total_cmp(b));101    assert_eq!(angles.len(), SAMPLES + 8);102    let levels: Vec<f64> = angles.iter().map(|t| height(*t)).collect();103    let plane: Vec<(f64, f64)> = angles104        .iter()105        .zip(&levels)106        .map(|(t, v)| {107            let r = 1.0 + STRETCH * squeeze(*v);108            (r * t.cos(), r * t.sin())109        })110        .collect();111    let (mut x0, mut x1, mut y0, mut y1) = (f64::MAX, f64::MIN, f64::MAX, f64::MIN);112    for p in &plane {113        x0 = x0.min(p.0);114        x1 = x1.max(p.0);115        y0 = y0.min(p.1);116        y1 = y1.max(p.1);117    }118119    let mut board = Board::square();120    let frame = board.frame(0.08);121    let scale = frame.w / (x1 - x0).max(y1 - y0);122    let (fx, fy) = frame.center();123    let cx = fx - scale * (x0 + x1) / 2.0;124    let cy = fy + scale * (y0 + y1) / 2.0;125    let at = |x: f64, y: f64| (cx + scale * x, cy - scale * y);126127    let out = ink::fade(ink::blue(), 0.22);128    let inn = ink::fade(ink::orange(), 0.22);129    let (bx0, by0) = at(x0, y1);130    let (bx1, by1) = at(x1, y0);131    for py in (by0.floor() as usize)..(by1.ceil() as usize).min(board.height) {132        for px in (bx0.floor() as usize)..(bx1.ceil() as usize).min(board.width) {133            let x = (px as f64 + 0.5 - cx) / scale;134            let y = (cy - py as f64 - 0.5) / scale;135            let rho = x.hypot(y);136            let u = y.atan2(x).rem_euclid(TAU) / TAU * SAMPLES as f64;137            let i = (u.floor() as usize) % SAMPLES;138            let w = u - u.floor();139            let r = reach[i] * (1.0 - w) + reach[(i + 1) % SAMPLES] * w;140            if rho > 1.0 && rho < r {141                board.blend(px, py, out, 1.0);142            } else if rho < 1.0 && rho > r {143                board.blend(px, py, inn, 1.0);144            }145        }146    }147148    board.ring(cx, cy, scale, 3.0, ink::dim());149    let mut run: Vec<(f64, f64)> = Vec::new();150    let mut up = levels[0] >= 0.0;151    for i in 0..=angles.len() {152        let k = i % angles.len();153        let p = at(plane[k].0, plane[k].1);154        let side = levels[k] >= 0.0;155        if side != up {156            run.push(p);157            board.polyline(&run, 5.0, if up { ink::blue() } else { ink::orange() });158            run.clear();159            up = side;160        }161        run.push(p);162    }163    board.polyline(&run, 5.0, if up { ink::blue() } else { ink::orange() });164165    for z in &on {166        let (px, py) = at(z.0, z.1);167        board.disc(px, py, 8.0, ink::fg());168    }169    for z in &off {170        let (px, py) = at(z.0, z.1);171        board.disc(px, py, 13.0, ink::yellow());172    }173    save("wiki-mahler-measure", &board)?;174    Ok(())175}