pairs.rs
4.9 kB · rust · 152 lines
1use crate::core::error::{value_error, Result};2use crate::math::moire::{layer, Layer, Spec};3use crate::num::factor::lcm;4use serde::{Deserialize, Serialize};56fn odd_blocks(span: u64, block: u64) -> u64 {7 block * (span / (2 * block)) + (span % (2 * block)).saturating_sub(block)8}910fn overlap(m: u64, n: u64) -> (u64, u64) {11 let grid = lcm(m as usize, n as usize) as u64;12 let (a, b) = (grid / m, grid / n);13 let both = (1..n)14 .step_by(2)15 .map(|k| odd_blocks((k + 1) * b, a) - odd_blocks(k * b, a))16 .sum();17 (both, grid)18}1920/// Returns the exact Pearson correlation of the flat carpet layers at two scales, area-weighted on their lcm grid.21///22/// A layer at scale n is lit where the row and the column of the n by n grid are not both odd.23/// The correlation is exactly zero when the two odd scales are coprime, and zero by convention below scale two, where a layer is constant.24///25/// ```26/// assert_eq!(mrlyrs::math::moire::pairs::correlation(3, 5), 0.0);27/// assert!(mrlyrs::math::moire::pairs::correlation(3, 9) > 0.0);28/// ```29pub fn correlation(m: usize, n: usize) -> f64 {30 let (m, n) = (m as u64, n as u64);31 let (hm, hn) = (m / 2, n / 2);32 if hm == 0 || hn == 0 {33 return 0.0;34 }35 let (both, grid) = overlap(m, n);36 let cross = (both * m * n) as i128;37 let solo = (hm * hn * grid) as i128;38 let gap = cross - solo;39 if gap == 0 {40 return 0.0;41 }42 let scale = (grid * m * n) as f64;43 let covariance = (gap as f64 / scale) * ((cross + solo) as f64 / scale);44 let variance = |half: u64, side: u64| {45 let share = (half * half) as f64 / (side * side) as f64;46 share * (1.0 - share)47 };48 covariance / (variance(hm, m) * variance(hn, n)).sqrt()49}5051/// The witness row of an odd scale: its correlation with every earlier odd scale from three, and the verdict the row gives.52#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]53pub struct Witness {54 /// The scale on trial.55 pub scale: usize,56 /// The earlier odd scales, three up to the scale less two.57 pub scales: Vec<usize>,58 /// The exact correlation with each earlier scale.59 pub row: Vec<f64>,60 /// The largest correlation in the row, zero for an empty row.61 pub max: f64,62 /// The earlier scale carrying the largest correlation, zero when the row is clear.63 pub at: usize,64 /// Whether the row is exactly clear, which is the scale being prime.65 pub prime: bool,66}6768/// Puts an odd scale of three or more on trial against every earlier odd scale.69///70/// ```71/// let trial = mrlyrs::math::moire::pairs::witness(9).unwrap();72/// assert_eq!((trial.scales, trial.at, trial.prime), (vec![3, 5, 7], 3, false));73/// ```74///75/// # Errors76///77/// Errors for a scale that is not odd and three or more.78pub fn witness(scale: usize) -> Result<Witness> {79 if scale < 3 || scale.is_multiple_of(2) {80 return value_error("the stack has odd scales from three.");81 }82 let scales: Vec<usize> = (3..scale).step_by(2).collect();83 let row: Vec<f64> = scales.iter().map(|&m| correlation(m, scale)).collect();84 let (mut max, mut at) = (0.0, 0);85 for (&m, &r) in scales.iter().zip(&row) {86 if r > max {87 (max, at) = (r, m);88 }89 }90 let prime = row.iter().all(|&r| r == 0.0);91 Ok(Witness {92 scale,93 scales,94 row,95 max,96 at,97 prime,98 })99}100101/// Returns the Pearson correlation of two rendered carpet layers on their lcm grid, sampled rather than integrated.102///103/// # Errors104///105/// Errors when either scale is zero.106pub fn sampled(m: usize, n: usize) -> Result<f64> {107 if m == 0 || n == 0 {108 return value_error("a sampled pair needs two scales of at least one.");109 }110 let size = lcm(m, n);111 let mask = |number| {112 let params = Layer {113 size,114 ..Layer::new(Spec::new(7, 2, 2), number)115 };116 layer(¶ms)117 };118 let (a, b) = (mask(m)?, mask(n)?);119 let mean = |v: &[bool]| v.iter().filter(|&&x| x).count() as f64 / v.len() as f64;120 let (ea, eb) = (mean(&a), mean(&b));121 let eab = a.iter().zip(&b).filter(|(&x, &y)| x && y).count() as f64 / a.len() as f64;122 Ok((eab - ea * eb) / (ea * (1.0 - ea) * eb * (1.0 - eb)).sqrt())123}124125#[cfg(test)]126mod tests {127 use super::*;128129 fn brute(m: u64, n: u64) -> (u64, u64) {130 let grid = lcm(m as usize, n as usize) as u64;131 let (a, b) = (grid / m, grid / n);132 let both = (0..grid)133 .filter(|&j| !(j / a).is_multiple_of(2) && !(j / b).is_multiple_of(2))134 .count() as u64;135 (both, grid)136 }137138 #[test]139 fn the_closed_count_matches_the_lcm_grid() {140 for m in 2..30u64 {141 for n in 2..30u64 {142 assert_eq!(overlap(m, n), brute(m, n), "{m} {n}");143 }144 }145 }146147 #[test]148 fn refuses_a_zero_scale() {149 assert!(sampled(0, 3).is_err());150 assert!(sampled(3, 0).is_err());151 }152}