mask.rs
4.4 kB · rust · 141 lines
1use crate::bang::factory;2use crate::bang::universe::Code;3use crate::two;4use mrlycore::errors::{value_error, Result};5use mrlycore::tensor::Tensor;67/// Builds the base-2 design mask a code names at an odd side grown to the given Kronecker level, its centre popped.8pub fn design_mask(dimension: usize, code: Code, number: usize, level: usize) -> Result<Tensor> {9 if !(1..=2).contains(&dimension) {10 return value_error("a mask lives in dimension 1 or 2.");11 }12 if number < 1 || number.is_multiple_of(2) {13 return value_error("the mask side must be odd.");14 }15 if level < 1 {16 return value_error("level must be at least 1.");17 }18 let mut mask = if dimension == 1 {19 factory::create(code, number, 1, 2, level)?20 } else {21 two::create(code, number, level, 0, 2)?.types().clone()22 };23 let centre = (number.pow(level as u32) - 1) / 2;24 mask.set(&vec![centre; dimension], 0);25 Ok(mask)26}2728fn gcd(a: i64, b: i64) -> i64 {29 let (mut a, mut b) = (a.abs(), b.abs());30 while b != 0 {31 let rest = a % b;32 a = b;33 b = rest;34 }35 a36}3738fn egcd(a: i64, b: i64) -> (i64, i64, i64) {39 if b == 0 {40 (a.abs(), if a < 0 { -1 } else { 1 }, 0)41 } else {42 let (g, s, t) = egcd(b, a % b);43 (g, t, s - (a / b) * t)44 }45}4647/// Returns the offsets a mask's filled sites take from its centre, the centre itself dropped.48pub fn mask_offsets(mask: &Tensor) -> Vec<Vec<i64>> {49 let dimension = mask.shape.len();50 let centre: Vec<i64> = mask.shape.iter().map(|&n| (n as i64 - 1) / 2).collect();51 let mut out = Vec::new();52 for flat in 0..mask.size() {53 if mask.bytes()[flat] != 1 {54 continue;55 }56 let mut rest = flat;57 let mut offset = vec![0i64; dimension];58 for axis in (0..dimension).rev() {59 offset[axis] = (rest % mask.shape[axis]) as i64 - centre[axis];60 rest /= mask.shape[axis];61 }62 if offset.iter().any(|&v| v != 0) {63 out.push(offset);64 }65 }66 out67}6869/// Returns the index of the lattice the mask offsets generate together with the centre, zero when they do not span the dimension.70pub fn lattice_index(mask: &Tensor) -> usize {71 let offsets = mask_offsets(mask);72 if mask.shape.len() == 1 {73 return offsets.iter().fold(0i64, |g, v| gcd(g, v[0])) as usize;74 }75 let (mut pivot, mut shear) = (0i64, 0i64);76 for offset in &offsets {77 let (g, s, t) = egcd(pivot, offset[0]);78 shear = s * shear + t * offset[1];79 pivot = g;80 }81 if pivot == 0 {82 return 0;83 }84 let mut rise = 0i64;85 for offset in &offsets {86 rise = gcd(rise, offset[1] - (offset[0] / pivot) * shear);87 }88 if rise == 0 {89 return 0;90 }91 (pivot * rise) as usize92}9394#[cfg(test)]95mod tests {96 use super::*;97 #[test]98 fn the_moore_mask_couples() {99 let mask = design_mask(2, 7, 3, 1).unwrap();100 assert_eq!((mask.shape.clone(), mask.sum()), (vec![3, 3], 8));101 assert_eq!(lattice_index(&mask), 1);102 }103 #[test]104 fn the_parity_tiles_alternate_by_side() {105 let read: Vec<usize> = [3, 5, 7, 9]106 .iter()107 .map(|&n| lattice_index(&design_mask(1, 1, n, 1).unwrap()))108 .collect();109 assert_eq!(read, vec![1, 2, 1, 2]);110 }111 #[test]112 fn the_cantor_tower_runs_one_two_one() {113 let read: Vec<usize> = (1..=3)114 .map(|level| lattice_index(&design_mask(1, 1, 3, level).unwrap()))115 .collect();116 assert_eq!(read, vec![1, 2, 1]);117 }118 #[test]119 fn the_diagonal_mask_decouples_and_the_von_neumann_one_does_not() {120 assert_eq!(lattice_index(&design_mask(2, 9, 3, 1).unwrap()), 2);121 assert_eq!(lattice_index(&design_mask(2, 6, 3, 1).unwrap()), 1);122 }123 #[test]124 fn a_rank_deficient_mask_reads_zero() {125 let flat = Tensor::of(vec![0, 0, 0, 1, 0, 1, 0, 0, 0], vec![3, 3]);126 assert_eq!(lattice_index(&flat), 0);127 assert_eq!(lattice_index(&Tensor::new(vec![3, 3])), 0);128 }129 #[test]130 fn even_sides_and_wide_dimensions_are_rejected() {131 assert!(design_mask(2, 7, 4, 1).is_err());132 assert!(design_mask(3, 7, 3, 1).is_err());133 assert!(design_mask(1, 1, 3, 0).is_err());134 }135 #[test]136 fn the_centre_is_popped_at_every_level() {137 let mask = design_mask(2, 7, 3, 2).unwrap();138 assert_eq!(mask.shape, vec![9, 9]);139 assert_eq!(mask.get(&[4, 4]), 0);140 }141}