paper-sponge-measurability.rs
8.2 kB · rust · 281 lines
1use mrlycore::errors::Result;2use mrlyfig::{ink, plot, save, Board, Frame, Grid};3use mrlymath::three::{carpet, slice};4use std::f64::consts::PI;56const LEVEL: usize = 4;7const SIDE: usize = 81;8const EPS: f64 = 1.0 / 36.0;9const LEVELS: usize = 200;10const DEPTH: usize = 40;11const DIGITS: usize = 40;12const CELLW: [f64; 3] = [3.0, 2.0, 3.0];13const SUMW: [f64; 3] = [0.0, 3.0, 5.0];14const BANDS: [(f64, f64, f64); 3] = [15 (1.0 / 12.0, 2.122718, 2.122723),16 (1.0 / 8.0, 2.134668, 2.135742),17 (1.0 / 6.0, 2.135019, 2.136794),18];19const TUBES: [(f64, f64, f64); 2] = [20 (1.0 / 12.0, 0.180947086, 0.180947093),21 (1.0 / 8.0, 0.234186414, 0.234701259),22];2324// THE HOLE INTEGRALS2526fn a0(t: f64, d: f64) -> f64 {27 if t >= d {28 return PI * d * d / 4.0;29 }30 t / 2.0 * (d * d - t * t).sqrt() + d * d / 2.0 * (t / d).asin()31}3233fn a1(t: f64, d: f64) -> f64 {34 if t >= d {35 return d * d * d / 3.0;36 }37 let y = d * d - t * t;38 t * t * (3.0 * d.powi(4) - 3.0 * d * d * t * t + t.powi(4)) / (3.0 * (d.powi(3) + y * y.sqrt()))39}4041fn seg(t0: f64, t1: f64, alpha: f64, beta: f64, d: f64) -> f64 {42 if t1 <= t0 {43 return 0.0;44 }45 alpha * (a0(t1, d) - a0(t0, d)) - beta * (a1(t1, d) - a1(t0, d))46}4748fn hole(s: f64, d: f64) -> f64 {49 4.0 * seg(0.0, s / 2.0, s, 2.0, d)50}5152fn partial(s: f64, c: f64, d: f64) -> f64 {53 let left = seg(0.0, (s / 2.0).min(c), s, 2.0, d);54 let right = seg((s - c).max(0.0), s / 2.0, s, 2.0, d);55 let bottom = if c <= s / 2.0 {56 seg(0.0, c, c, 1.0, d)57 } else {58 seg(0.0, s - c, c, 1.0, d) + seg(s - c, s / 2.0, s, 2.0, d)59 };60 left + right + 2.0 * bottom61}6263fn digit(q: f64) -> usize {64 ((q - 3.0 * (q / 3.0).floor()) as usize).min(2)65}6667fn weight(mut i: f64, digits: usize) -> f64 {68 let mut w = 1.0;69 for _ in 0..digits {70 w *= CELLW[digit(i)];71 i = (i / 3.0).floor();72 }73 w74}7576fn below(count: f64, digits: usize) -> f64 {77 if count >= 3f64.powi(digits as i32) {78 return 8f64.powi(digits as i32);79 }80 let (mut total, mut prefix) = (0.0, 1.0);81 for d in (0..digits).rev() {82 let k = digit((count / 3f64.powi(d as i32)).floor());83 total += prefix * SUMW[k] * 8f64.powi(d as i32);84 prefix *= CELLW[k];85 }86 total87}8889fn wall_half(delta: f64) -> f64 {90 let mut total = 0.0;91 for m in 1..=LEVELS {92 total += 8f64.powi(m as i32 - 1) * hole(3f64.powi(-(m as i32) - 1), delta);93 }94 total / 2.095}9697fn strip(delta: f64) -> f64 {98 let mut total = 0.0;99 for m in 1..=LEVELS {100 let s = 3f64.powi(-(m as i32) - 1);101 let ratio = delta / s;102 let columns = 3f64.powi(m as i32 - 1);103 let full = ((ratio - 2.0) / 3.0).floor() + 1.0;104 if full > 0.0 {105 total += below(full.min(columns), m - 1) * hole(s, delta);106 }107 let cut = ((ratio - 1.0) / 3.0).floor();108 if cut >= 0.0109 && cut < columns110 && (3.0 * cut + 1.0) * s < delta111 && delta < (3.0 * cut + 2.0) * s112 {113 total += weight(cut, m - 1) * partial(s, delta - (3.0 * cut + 1.0) * s, delta);114 }115 }116 total117}118119fn tube(delta: f64) -> f64 {120 (PI + 8.0) * delta * delta - 8.0 * 2f64.sqrt() * delta.powi(3)121 + 48.0 * (wall_half(delta) - strip(delta))122}123124fn periodic(eps: f64) -> f64 {125 let dim = 20f64.ln() / 3f64.ln();126 let mut total = 20.0 / 27.0;127 for l in 0..=DEPTH {128 total += (27f64 / 20.0).powi(l as i32) * tube(eps / 3f64.powi(l as i32));129 }130 eps.powf(dim - 3.0) * total131}132133// THE DISTANCE IN THE MIDPLANE134135fn split(x: f64) -> (usize, f64) {136 let d = (3.0 * x).floor().min(2.0);137 (d as usize, 3.0 * x - d)138}139140fn carpet_dist(mut a: f64, mut b: f64) -> f64 {141 let mut s = 1.0 / 3.0;142 for _ in 0..DIGITS {143 let ((da, fa), (db, fb)) = (split(a), split(b));144 if da == 1 && db == 1 {145 return s * fa.min(1.0 - fa).min(fb).min(1.0 - fb);146 }147 a = fa;148 b = fb;149 s /= 3.0;150 }151 0.0152}153154fn wall(u: f64, along: f64, across: f64) -> f64 {155 let d = carpet_dist(3.0 * along, 3.0 * across) / 3.0;156 (u * u + d * d).sqrt()157}158159fn arm_dist(along: f64, u: f64) -> f64 {160 let side = wall(u, along, 1.0 / 6.0).min(wall(1.0 / 3.0 - u, along, 1.0 / 6.0));161 let far = wall(1.0 / 6.0, along, u);162 side.min(far)163}164165fn plus_dist(x: f64, y: f64) -> f64 {166 let (third, two) = (1.0 / 3.0, 2.0 / 3.0);167 let mid = |v: f64| (third..=two).contains(&v);168 if mid(x) && mid(y) {169 let mut best = f64::MAX;170 for cx in [third, two] {171 for cy in [third, two] {172 best = best.min(((x - cx).powi(2) + (y - cy).powi(2)).sqrt());173 }174 }175 let edge = (x - third)176 .abs()177 .min((x - two).abs())178 .min((y - third).abs())179 .min((y - two).abs());180 return best.min((1.0 / 36.0 + edge * edge).sqrt());181 }182 if mid(y) {183 let along = if x < third { x } else { 1.0 - x };184 return arm_dist(along, y - third);185 }186 let along = if y < third { y } else { 1.0 - y };187 arm_dist(along, x - third)188}189190fn dist(mut x: f64, mut y: f64) -> f64 {191 let mut scale = 1.0;192 for _ in 0..DIGITS {193 let ((dx, fx), (dy, fy)) = (split(x), split(y));194 if dx == 1 || dy == 1 {195 return scale * plus_dist(x, y);196 }197 x = fx;198 y = fy;199 scale /= 3.0;200 }201 0.0202}203204// THE MARKS205206fn bands(board: &mut Board, frame: Frame) {207 plot::axis(board, frame, ink::line());208 let area = frame.inset(24.0);209 let (lo, hi) = (2.120, 2.140);210 let y_of = |v: f64| area.y + area.h * (hi - v) / (hi - lo);211 let width = area.w * 0.3;212 let (a, b) = (BANDS[0], BANDS[2]);213 for (k, band) in [a, b].iter().enumerate() {214 let x = area.x + area.w * (0.25 + 0.5 * k as f64) - width / 2.0;215 let (top, foot) = (y_of(band.2), y_of(band.1));216 let h = (foot - top).max(4.0);217 board.rect(x, top, width, h, ink::orange());218 }219 let (cx, gap_lo, gap_hi) = (area.x + area.w / 2.0, y_of(a.2) - 2.0, y_of(b.1) + 2.0);220 board.segment((cx, gap_lo), (cx, gap_hi), 2.0, ink::dim());221 board.segment((cx - 10.0, gap_lo), (cx + 10.0, gap_lo), 2.0, ink::dim());222 board.segment((cx - 10.0, gap_hi), (cx + 10.0, gap_hi), 2.0, ink::dim());223}224225fn main() -> Result<()> {226 let sponge = carpet(3, LEVEL)?;227 assert_eq!(sponge.types().sum(), 160000);228 let dust = slice(&sponge, 2, (SIDE - 1) / 2)?;229 let cells = dust.types().bytes().to_vec();230 assert_eq!(cells.len(), SIDE * SIDE);231 assert_eq!(cells.iter().filter(|&&b| b != 0).count(), 256);232233 let ident = (PI + 8.0) / 36.0 - 2f64.sqrt() / 27.0;234 assert!((tube(1.0 / 6.0) - ident).abs() < 1e-9);235 for (delta, lo, hi) in TUBES {236 let t = tube(delta);237 assert!(lo <= t && t <= hi);238 }239 let mut values = [0.0; 3];240 for (k, (eps, lo, hi)) in BANDS.iter().enumerate() {241 values[k] = periodic(*eps);242 assert!(*lo <= values[k] && values[k] <= *hi);243 }244 assert!(values[2] - values[0] >= 0.012296);245 assert!((dist(0.5, 0.5) - 2f64.sqrt() / 6.0).abs() < 1e-15);246 assert!((dist(1.0 / 6.0, 1.0 / 6.0) - 2f64.sqrt() / 18.0).abs() < 1e-15);247248 let mut board = Board::square();249 let margin = (board.width as f64 * 0.08).round();250 let plate = 600.0;251 let sheet = Frame::new(margin, margin, plate, plate);252 board.rect(sheet.x, sheet.y, sheet.w, sheet.h, ink::panel());253 let n = plate as usize;254 let blue = ink::blue();255 for j in 0..n {256 for i in 0..n {257 let (x, y) = ((i as f64 + 0.5) / plate, (j as f64 + 0.5) / plate);258 let cover = (0.5 + (EPS - dist(x, y)) * plate).clamp(0.0, 1.0);259 if cover > 0.0 {260 board.blend(sheet.x as usize + i, sheet.y as usize + j, blue, cover);261 }262 }263 }264 let lattice = Grid::new(sheet, SIDE, SIDE, 0.0);265 for row in 0..SIDE {266 for col in 0..SIDE {267 if cells[row * SIDE + col] != 0 {268 lattice.fill(&mut board, col, row, ink::fg());269 }270 }271 }272 let panel = Frame::new(273 board.width as f64 - margin - 240.0,274 board.height as f64 - margin - 240.0,275 240.0,276 240.0,277 );278 bands(&mut board, panel);279 save("paper-sponge-measurability", &board)?;280 Ok(())281}