frame.rs

7.0 kB · rust · 235 lines

1use crate::lattice::{cell, column, row, Rule};2use crate::sums::{mean, odds};34#[derive(Clone, Copy)]5pub enum Line {6    X,7    Z,8    W,9    D,10    K,11}1213impl Line {14    pub fn name(self) -> &'static str {15        match self {16            Line::X => "X",17            Line::Z => "Z",18            Line::W => "X+Z",19            Line::D => "X-Z",20            Line::K => "2X+Z",21        }22    }2324    fn at(self, x: f64, z: f64) -> f64 {25        match self {26            Line::X => x,27            Line::Z => z,28            Line::W => x + z,29            Line::D => x - z,30            Line::K => 2.0 * x + z,31        }32    }33}3435pub struct Frame {36    pub side: usize,37    pub layers: usize,38    pub values: Vec<f64>,39    pub whole: Vec<bool>,40    pub points: Vec<f64>,41}4243pub fn points(side: usize) -> Vec<f64> {44    (0..side)45        .map(|step| (step as f64 + 0.5) / side as f64)46        .collect()47}4849pub fn stack(limit: usize, rule: &Rule, side: usize) -> Frame {50    let points = points(side);51    let mut values = vec![0.0f64; side * side];52    let mut whole = vec![true; side * side];53    let mut layers = 0;54    let mut line = vec![0.0f64; side];55    let mut reach = vec![true; side];56    for number in odds(limit) {57        let n = number as i64;58        let columns: Vec<i64> = points.iter().map(|p| column(*p, n)).collect();59        let rows: Vec<i64> = points.iter().map(|p| row(*p, n)).collect();60        let mut start = 0;61        while start < side {62            let x = columns[start];63            let mut end = start;64            while end < side && columns[end] == x {65                end += 1;66            }67            for (col, z) in rows.iter().enumerate() {68                let found = cell(rule, n, x, *z);69                line[col] = f64::from(found.unwrap_or(false));70                reach[col] = found.is_some();71            }72            let clipped = reach.iter().any(|ok| !ok);73            for slot in start..end {74                let base = slot * side;75                for (value, add) in values[base..base + side].iter_mut().zip(&line) {76                    *value += add;77                }78                if clipped {79                    for (keep, ok) in whole[base..base + side].iter_mut().zip(&reach) {80                        *keep &= ok;81                    }82                }83            }84            start = end;85        }86        layers += 1;87    }88    for value in values.iter_mut() {89        *value /= layers as f64;90    }91    Frame {92        side,93        layers,94        values,95        whole,96        points,97    }98}99100impl Frame {101    pub fn window(&self, line: Line, keep: impl Fn(f64) -> bool) -> f64 {102        let mut kept = Vec::new();103        for row in 0..self.side {104            for col in 0..self.side {105                let at = row * self.side + col;106                if self.whole[at] && keep(line.at(self.points[row], self.points[col])) {107                    kept.push(self.values[at]);108                }109            }110        }111        mean(&kept)112    }113114    pub fn hexmean(&self) -> f64 {115        let kept: Vec<f64> = self116            .values117            .iter()118            .zip(&self.whole)119            .filter(|(_, keep)| **keep)120            .map(|(value, _)| *value)121            .collect();122        mean(&kept)123    }124125    pub fn band(&self, line: Line, position: f64, width: f64) -> [f64; 3] {126        let left = self.window(line, |at| at >= position - width && at < position);127        let right = self.window(line, |at| at > position && at <= position + width);128        let centre = self.window(line, |at| (at - position).abs() <= width);129        [left, right, centre]130    }131}132133pub fn plateau(limit: usize, rule: &Rule, side: usize) -> (f64, f64) {134    let frame = stack(limit, rule, side);135    let background = frame.hexmean();136    let mut sums = [vec![0.0f64; side], vec![0.0f64; side]];137    let mut whole = [vec![true; side], vec![true; side]];138    for number in odds(limit) {139        let n = number as i64;140        for (slot, x) in [n - 1, n].into_iter().enumerate() {141            for (at, point) in frame.points.iter().enumerate() {142                match cell(rule, n, x, row(*point, n)) {143                    Some(fill) => sums[slot][at] += f64::from(fill),144                    None => whole[slot][at] = false,145                }146            }147        }148    }149    let strip = |slot: usize| {150        let kept: Vec<f64> = sums[slot]151            .iter()152            .zip(&whole[slot])153            .filter(|(_, keep)| **keep)154            .map(|(value, _)| value / frame.layers as f64)155            .collect();156        mean(&kept) - background157    };158    (strip(0), strip(1))159}160161pub fn quarter(rule: &Rule) {162    println!("carpet ideal frame at N = 55, 3601 samples per axis, one-sided bands of width 0.018");163    let frame = stack(55, rule, 3601);164    let background = frame.hexmean();165    println!("  hexagon mean {background:.6}");166    let spec = [167        (Line::X, 0.25),168        (Line::X, 0.75),169        (Line::X, 1.0 / 3.0),170        (Line::X, 0.2),171        (Line::X, 0.5),172        (Line::Z, 0.25),173        (Line::D, 0.25),174        (Line::W, 1.25),175    ];176    for (line, position) in spec {177        let [left, right, _] = frame.band(line, position, 0.018);178        println!(179            "  {} = {position:.4}: left {:+.6}  right {:+.6}",180            line.name(),181            left - background,182            right - background183        );184    }185    println!("quarter line X = 1/4, one-sided strips straight at the line, 3601 samples");186    for limit in [151usize, 301, 601, 1201] {187        let (left, right) = plateau(limit, rule, 3601);188        println!(189            "  N = {limit:4}: left {left:+.5}  right {right:+.5}  mean magnitude {:.5}  (limit 1/8)",190            (left.abs() + right.abs()) / 2.0191        );192    }193}194195pub fn void_star(rule: &Rule) {196    println!(197        "void ideal frame at N = 55, 3601 samples per axis, centred bands of half-width 0.004"198    );199    let frame = stack(55, rule, 3601);200    let background = frame.hexmean();201    println!("  hexagon mean {background:.6}  (limit 1/4)");202    for (line, position) in [203        (Line::X, 0.5),204        (Line::Z, 0.5),205        (Line::W, 1.0),206        (Line::D, 0.0),207        (Line::K, 1.5),208    ] {209        let [_, _, centre] = frame.band(line, position, 0.004);210        println!(211            "  {} = {position:.2}: ink {centre:.6}  ratio to mean {:.3}",212            line.name(),213            centre / background214        );215    }216}217218pub fn fading(rule: &Rule) {219    println!("carpet main diagonal X = Z, band half-width 0.01, 2801 samples per axis");220    let mut scaled = Vec::new();221    for count in [28usize, 100, 200, 400] {222        let frame = stack(2 * count - 1, rule, 2801);223        let excess = frame.window(Line::D, |at| at.abs() <= 0.01) - frame.hexmean();224        println!(225            "  L = {count:3}: excess {excess:+.6}  excess * L {:+.4}",226            excess * count as f64227        );228        scaled.push(excess * count as f64);229    }230    println!(231        "  slope of excess * L against ln L: {:+.4} (100 to 200)  {:+.4} (200 to 400)",232        (scaled[2] - scaled[1]) / 2f64.ln(),233        (scaled[3] - scaled[2]) / 2f64.ln()234    );235}