models.rs
9.0 kB · rust · 287 lines
1use crate::core::cell::Cell;2use crate::core::cell::Mode;3use crate::core::colors::Color;4use crate::core::error::{shape_error, value_error, Result};5use crate::core::tensor::{Dtype, Tensor};6use serde::{Deserialize, Serialize};7use std::collections::HashMap;89/// The two-dimensional cell.10pub type Cell2d = CellNd<2>;11/// The three-dimensional cell.12pub type Cell3d = CellNd<3>;1314/// Returns the narrowest unsigned dtype that holds the peak value.15///16/// ```17/// use mrlyrs::core::tensor::Dtype;18/// assert_eq!(mrlyrs::math::cell::models::dtype_for(255), Dtype::U8);19/// assert_eq!(mrlyrs::math::cell::models::dtype_for(256), Dtype::U16);20/// assert_eq!(mrlyrs::math::cell::models::dtype_for(70000), Dtype::U32);21/// ```22pub fn dtype_for(peak: i64) -> Dtype {23 if peak <= Dtype::U8.max() {24 Dtype::U825 } else if peak <= Dtype::U16.max() {26 Dtype::U1627 } else {28 Dtype::U3229 }30}3132/// Returns the narrowest count dtype that fits the mask's popcount.33pub fn counting_dtype(mask: &Tensor) -> Dtype {34 dtype_for(mask.sum() as i64)35}3637/// A cell whose tensor is pinned to N dimensions.38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]39pub struct CellNd<const N: usize> {40 /// The wrapped cell.41 pub cell: Cell,42}4344impl<const N: usize> CellNd<N> {45 /// Builds a cell from an N-dimensional tensor of types.46 ///47 /// # Errors48 ///49 /// Errors when the tensor's rank is not N.50 pub fn new(types: Tensor) -> Result<CellNd<N>> {51 if types.shape.len() != N {52 return shape_error(format!(53 "a {N}d cell needs a {N}d tensor, got {}d.",54 types.shape.len()55 ));56 }57 Ok(CellNd {58 cell: Cell::new(types),59 })60 }61 /// Returns the size of axis 1.62 pub fn width(&self) -> usize {63 self.cell.types.shape[1]64 }65 /// Returns the size of axis 0.66 pub fn height(&self) -> usize {67 self.cell.types.shape[0]68 }69 /// Returns the tensor of types.70 pub fn types(&self) -> &Tensor {71 &self.cell.types72 }73 /// Swaps filled and empty sites.74 pub fn invert(self) -> CellNd<N> {75 CellNd {76 cell: self.cell.invert(),77 }78 }79 /// Inverts the cell.80 pub fn anti(self) -> CellNd<N> {81 self.invert()82 }83 /// Wraps the cell in count layers of the given value on every side.84 pub fn pad(self, count: usize, value: u8) -> CellNd<N> {85 CellNd {86 cell: self.cell.pad(count, value),87 }88 }89 /// Deepens the cell into its level-fold fractal.90 ///91 /// # Errors92 ///93 /// Errors below level one.94 pub fn fractal(self, level: usize) -> Result<CellNd<N>> {95 Ok(CellNd {96 cell: self.cell.fractal(level)?,97 })98 }99 /// Tags each site with its ring distance from the center.100 pub fn layers(self) -> CellNd<N> {101 CellNd {102 cell: self.cell.layers(Dtype::U8),103 }104 }105 /// Tags each site with its count of masked neighbors matching the target, wrapping on request.106 ///107 /// # Errors108 ///109 /// Errors when the mask does not match the cell's rank.110 pub fn neighbors(self, mask: &Tensor, target: u8, wrap: bool) -> Result<CellNd<N>> {111 let dtype = counting_dtype(mask);112 Ok(CellNd {113 cell: self.cell.neighbors(mask, target, wrap, dtype)?,114 })115 }116 /// Maps each site to one at or above the threshold, zero below.117 pub fn binarize(self, threshold: u8) -> CellNd<N> {118 CellNd {119 cell: self.cell.binarize(threshold),120 }121 }122 /// Binarizes the cell at the threshold Otsu's method picks.123 pub fn binarize_otsu(self) -> CellNd<N> {124 CellNd {125 cell: self.cell.binarize_otsu(),126 }127 }128 /// Rounds each site to the mean of its masked neighborhood, wrapping on request.129 ///130 /// # Errors131 ///132 /// Errors when the mask does not match the cell's rank.133 pub fn blur(self, mask: &Tensor, wrap: bool) -> Result<CellNd<N>> {134 Ok(CellNd {135 cell: self.cell.blur(mask, wrap)?,136 })137 }138 /// Writes the value wherever the tiled mask is nonzero.139 ///140 /// # Errors141 ///142 /// Errors when the mask does not tile the cell.143 pub fn perforate(self, mask: &Tensor, value: u8) -> Result<CellNd<N>> {144 Ok(CellNd {145 cell: self.cell.perforate(mask, value)?,146 })147 }148 /// Returns the Kronecker product of the two cells.149 pub fn combine(&self, other: &CellNd<N>) -> CellNd<N> {150 CellNd {151 cell: self.cell.combine(&other.cell),152 }153 }154 /// Colors each site by its type through the mapping in the given mode.155 pub fn paint(self, mapping: &HashMap<u8, Vec<Color>>, mode: Mode) -> CellNd<N> {156 CellNd {157 cell: self.cell.paint(mapping, mode),158 }159 }160}161162impl CellNd<2> {163 /// Rotates the cell k quarter turns in the plane.164 ///165 /// # Errors166 ///167 /// Errors for a cell without two axes.168 pub fn rotate(self, k: usize) -> Result<Cell2d> {169 Ok(CellNd {170 cell: self.cell.rotate(k, (0, 1))?,171 })172 }173 /// Repeats the cell into a width-by-height array of copies.174 ///175 /// # Errors176 ///177 /// Errors for a cell without two axes.178 pub fn tile(self, width: usize, height: usize) -> Result<Cell2d> {179 Ok(CellNd {180 cell: self.cell.tile(&[height, width])?,181 })182 }183}184185impl CellNd<3> {186 /// Returns the size of axis 2.187 pub fn depth(&self) -> usize {188 self.cell.types.shape[2]189 }190 /// Rotates the cell k quarter turns about the given pair of axes.191 ///192 /// # Errors193 ///194 /// Errors for axes the cell does not hold.195 pub fn rotate(self, k: usize, axes: (usize, usize)) -> Result<Cell3d> {196 Ok(CellNd {197 cell: self.cell.rotate(k, axes)?,198 })199 }200 /// Turns the cell into one of the 24 cube orientations.201 ///202 /// # Errors203 ///204 /// Errors past orientation twenty-three.205 pub fn orient(self, index: usize) -> Result<Cell3d> {206 let table = crate::math::three::orientations();207 match table.get(index) {208 Some(&(a, b, c)) => self.rotate(a, (1, 2))?.rotate(b, (0, 2))?.rotate(c, (0, 1)),209 None => value_error(format!("orientation index {index} out of range (0..23).")),210 }211 }212 /// Repeats the cell into a width-by-height-by-depth array of copies.213 ///214 /// # Errors215 ///216 /// Errors for a cell without three axes.217 pub fn tile(self, width: usize, height: usize, depth: usize) -> Result<Cell3d> {218 Ok(CellNd {219 cell: self.cell.tile(&[height, width, depth])?,220 })221 }222}223224#[cfg(test)]225mod tests {226 use super::*;227 use crate::math::atoms;228 #[test]229 fn binarize_wrapper_thresholds_pointwise() {230 let cell = Cell2d::new(atoms::carpet_2d(3)).unwrap();231 let binarized = cell.clone().binarize(1);232 assert_eq!(binarized.types(), cell.types());233 }234 #[test]235 fn blur_keeps_the_shape_at_every_rank() {236 let flat = Cell2d::new(atoms::carpet_2d(3)).unwrap();237 let blurred = flat238 .clone()239 .blur(&Tensor::full(vec![3, 3], 1), true)240 .unwrap();241 assert_eq!(blurred.types().shape, flat.types().shape);242 let cube = Cell3d::new(atoms::carpet_3d(3)).unwrap();243 let blurred = cube244 .clone()245 .blur(&Tensor::full(vec![3, 3, 3], 1), true)246 .unwrap();247 assert_eq!(blurred.types().shape, cube.types().shape);248 }249 #[test]250 fn a_zero_mask_perforates_nothing_at_every_rank() {251 let flat = Cell2d::new(atoms::carpet_2d(3)).unwrap();252 let mask = Tensor::new(flat.types().shape.clone());253 assert_eq!(254 flat.clone().perforate(&mask, 5).unwrap().types(),255 flat.types()256 );257 let cube = Cell3d::new(atoms::carpet_3d(3)).unwrap();258 let mask = Tensor::new(cube.types().shape.clone());259 assert_eq!(260 cube.clone().perforate(&mask, 5).unwrap().types(),261 cube.types()262 );263 }264 #[test]265 fn counting_dtype_widens_with_the_mask() {266 assert_eq!(counting_dtype(&Tensor::full(vec![3, 3], 1)), Dtype::U8);267 assert_eq!(counting_dtype(&Tensor::full(vec![15, 15], 1)), Dtype::U8);268 assert_eq!(counting_dtype(&Tensor::full(vec![17, 17], 1)), Dtype::U16);269 assert_eq!(counting_dtype(&Tensor::full(vec![63, 63], 1)), Dtype::U16);270 }271 #[test]272 fn neighbors_wrapper_survives_a_wide_mask() {273 let mut mask = Tensor::full(vec![17, 17], 1);274 mask.set(&[8, 8], 0).unwrap();275 let grid = Cell2d::new(Tensor::full(vec![21, 21], 1)).unwrap();276 let counted = grid.neighbors(&mask, 1, true).unwrap();277 let tags = counted.cell.tags.as_ref().unwrap();278 assert_eq!(tags.at(0), 17 * 17 - 1);279 }280281 #[test]282 fn refuses_a_tensor_of_the_wrong_rank() {283 assert!(Cell2d::new(Tensor::new(vec![2, 2, 2])).is_err());284 assert!(Cell3d::new(Tensor::new(vec![2, 2])).is_err());285 assert!(CellNd::<1>::new(Tensor::new(vec![2, 2])).is_err());286 }287}