paper-franel-converse.rs
5.5 kB · rust · 203 lines
1use mrlycore::errors::Result;2use mrlycore::Color;3use mrlyfig::{ink, plot, save, Board, Frame};4use mrlynum::design::elements;5use mrlynum::factor::{gcd, mobius_sieve};6use mrlynum::lattice::farey;78const BASE: u64 = 3;9const DIGITS: [u64; 2] = [0, 1];10const ORDER: usize = 81;1112// THE NODES1314fn nodes_of(dens: &[usize]) -> Vec<(usize, usize)> {15 let mut out = Vec::new();16 for &b in dens {17 for a in 1..=b {18 if gcd(a, b) == 1 {19 out.push((a, b));20 }21 }22 }23 out.sort_by(|p, q| (p.0 * q.1).cmp(&(q.0 * p.1)));24 out25}2627fn sawtooth(nodes: &[(usize, usize)]) -> Vec<f64> {28 let m = nodes.len() as f64;29 nodes30 .iter()31 .enumerate()32 .map(|(j, &(a, b))| a as f64 / b as f64 - (j + 1) as f64 / m)33 .collect()34}3536fn kernel(dilates: &[i64]) -> f64 {37 let mut total = 0.0;38 for d in 1..dilates.len() {39 if dilates[d] == 0 {40 continue;41 }42 for e in 1..dilates.len() {43 if dilates[e] == 0 {44 continue;45 }46 let g = gcd(d, e) as f64;47 total += g * g / (d as f64 * e as f64) * (dilates[d] * dilates[e]) as f64;48 }49 }50 total51}5253fn dilated(holds: &[bool], mu: &[i8]) -> Vec<i64> {54 let q = holds.len() - 1;55 let mut out = vec![0i64; q + 1];56 for d in 1..=q {57 for c in 1..=q / d {58 if holds[d * c] {59 out[d] += mu[c] as i64;60 }61 }62 }63 out64}6566// THE MARKS6768fn ticks(board: &mut Board, area: Frame, nodes: &[(usize, usize)], thick: f64, color: Color) {69 let reach = (ORDER as f64).ln();70 let foot = area.y + area.h;71 for &(a, b) in nodes {72 let x = area.x + area.w * a as f64 / b as f64;73 let h = area.h * (1.0 - 0.82 * (b as f64).ln() / reach);74 board.segment((x, foot), (x, foot - h), thick, color);75 }76}7778fn trace(79 board: &mut Board,80 area: Frame,81 nodes: &[(usize, usize)],82 delta: &[f64],83 span: f64,84 thick: f64,85 color: Color,86) {87 let pts: Vec<(f64, f64)> = nodes88 .iter()89 .zip(delta)90 .map(|(&(a, b), &v)| {91 (92 area.x + area.w * a as f64 / b as f64,93 area.y + area.h * (0.5 - 0.5 * v / span),94 )95 })96 .collect();97 board.polyline(&pts, thick, color);98}99100fn main() -> Result<()> {101 let design: Vec<usize> = elements(BASE, &DIGITS, 5)102 .into_iter()103 .filter(|&n| n as usize <= ORDER)104 .map(|n| n as usize)105 .collect();106 assert_eq!(design.len(), 16);107 assert_eq!(design[..5], [1, 3, 4, 9, 10]);108 let mut holds = vec![false; ORDER + 1];109 for &b in &design {110 holds[b] = true;111 }112 let thin = nodes_of(&design);113 assert_eq!(thin.len(), 241);114 let full: Vec<(usize, usize)> = farey(ORDER)115 .iter()116 .filter(|n| n.num > 0)117 .map(|n| (n.num as usize, n.den as usize))118 .collect();119 assert_eq!(full.len(), 2020);120 assert_eq!(full, nodes_of(&(1..=ORDER).collect::<Vec<usize>>()));121122 let delta_thin = sawtooth(&thin);123 let delta_full = sawtooth(&full);124 let s2_thin: f64 = delta_thin.iter().map(|v| v * v).sum();125 let s2_full: f64 = delta_full.iter().map(|v| v * v).sum();126 assert!((s2_thin - 0.004_002_104_850).abs() < 1e-9);127 assert!((s2_full - 0.006_524_653_839).abs() < 1e-9);128129 let mu = mobius_sieve(ORDER);130 let x_thin = dilated(&holds, &mu);131 assert_eq!(x_thin[1], -2);132 assert_eq!(x_thin.iter().map(|v| v * v).sum::<i64>(), 19);133 let g_thin = kernel(&x_thin);134 assert!((g_thin - 12.574_087).abs() < 1e-5);135 assert!((g_thin - (12.0 * 241.0 * s2_thin + 1.0)).abs() < 1e-8);136 let x_full = dilated(&[true; ORDER + 1], &mu);137 assert_eq!(x_full[1], -4);138 let g_full = kernel(&x_full);139 assert!((g_full - 159.157_609).abs() < 1e-5);140 assert!((g_full - (12.0 * 2020.0 * s2_full + 1.0)).abs() < 1e-8);141142 let mut board = Board::square();143 let frame = board.frame(0.08);144 let gap = 26.0;145 let row = (frame.h - 2.0 * gap) * 0.21;146 let top = Frame::new(frame.x, frame.y, frame.w, row);147 let mid = Frame::new(frame.x, frame.y + row + gap, frame.w, row);148 let foot_y = frame.y + 2.0 * (row + gap);149 let foot = Frame::new(frame.x, foot_y, frame.w, frame.y + frame.h - foot_y);150151 plot::baseline(&mut board, top, ink::line());152 plot::baseline(&mut board, mid, ink::line());153 plot::axis(&mut board, foot, ink::line());154 let zero = foot.y + foot.h / 2.0;155 board.segment((foot.x, zero), (foot.x + foot.w, zero), 1.0, ink::line());156157 ticks(&mut board, top.inset(2.0), &thin, 2.2, ink::blue());158 ticks(159 &mut board,160 mid.inset(2.0),161 &full,162 1.0,163 ink::fade(ink::dim(), 0.55),164 );165166 let inner = foot.inset(16.0);167 let span = delta_thin.iter().fold(0.0f64, |a, v| a.max(v.abs())) * 1.12;168 trace(169 &mut board,170 inner,171 &full,172 &delta_full,173 span,174 1.2,175 ink::fade(ink::dim(), 0.7),176 );177 trace(178 &mut board,179 inner,180 &thin,181 &delta_thin,182 span,183 2.4,184 ink::orange(),185 );186 plot::dots(187 &mut board,188 &thin189 .iter()190 .zip(&delta_thin)191 .map(|(&(a, b), &v)| {192 (193 inner.x + inner.w * a as f64 / b as f64,194 inner.y + inner.h * (0.5 - 0.5 * v / span),195 )196 })197 .collect::<Vec<_>>(),198 2.6,199 ink::blue(),200 );201 save("paper-franel-converse", &board)?;202 Ok(())203}