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}