measure.rs
4.3 kB · rust · 129 lines
1use mrlycore::errors::{value_error, Result};23/// The cost class of a measure along the level axis.4#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]5pub enum Cost {6 /// A closed form: microseconds a term.7 Closed,8 /// A convolution over the diagonal profile: milliseconds a term.9 Convolved,10 /// A rendered grid of `n^(D L)` cells.11 Grid,12}1314/// One integer reading of a design.15#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]16pub enum Measure {17 /// The filled cells.18 Fills,19 /// The empty cells.20 Voids,21 /// The exposed faces: the perimeter in the plane, the surface in space.22 Surface,23 /// The most filled cells on one diagonal plane.24 Peak,25 /// The diagonal planes holding a filled cell.26 Heights,27 /// The distinct corners the filled cells touch.28 Vertices,29 /// The distinct unit edges the filled cells carry.30 Edges,31 /// The distinct unit faces the filled cells carry.32 Faces,33 /// The Euler characteristic of the filled complex.34 Euler,35 /// The filled triangles of the diagonal slice of a cube.36 Triangles,37 /// The holes of the diagonal slice of a cube.38 Holes,39 /// The connected pieces of the diagonal slice of a cube.40 Pieces,41}4243impl Measure {44 /// Every measure, in ledger order.45 pub const ALL: [Measure; 12] = [46 Measure::Fills,47 Measure::Voids,48 Measure::Surface,49 Measure::Peak,50 Measure::Heights,51 Measure::Vertices,52 Measure::Edges,53 Measure::Faces,54 Measure::Euler,55 Measure::Triangles,56 Measure::Holes,57 Measure::Pieces,58 ];59 /// Returns the measure's one-word name.60 pub fn slug(self) -> &'static str {61 match self {62 Measure::Fills => "fills",63 Measure::Voids => "voids",64 Measure::Surface => "surface",65 Measure::Peak => "peak",66 Measure::Heights => "heights",67 Measure::Vertices => "vertices",68 Measure::Edges => "edges",69 Measure::Faces => "faces",70 Measure::Euler => "euler",71 Measure::Triangles => "triangles",72 Measure::Holes => "holes",73 Measure::Pieces => "pieces",74 }75 }76 /// Parses a one-word name back into its measure, or an error for any other word.77 pub fn parse(slug: &str) -> Result<Measure> {78 Measure::ALL79 .into_iter()80 .find(|measure| measure.slug() == slug)81 .map_or_else(|| value_error(format!("unknown measure {slug:?}.")), Ok)82 }83 /// Returns the measure's cost class along the level axis.84 pub fn cost(self) -> Cost {85 match self {86 Measure::Fills | Measure::Voids | Measure::Surface => Cost::Closed,87 Measure::Peak | Measure::Heights => Cost::Convolved,88 _ => Cost::Grid,89 }90 }91 /// Returns whether the measure reads a design of the dimension and base.92 pub fn applies(self, dimension: usize, base: usize) -> bool {93 match self {94 Measure::Fills | Measure::Voids | Measure::Surface => dimension >= 1,95 Measure::Peak | Measure::Heights => dimension >= 2,96 Measure::Vertices | Measure::Edges | Measure::Euler => dimension == 2 || dimension == 3,97 Measure::Faces => dimension == 3,98 Measure::Triangles | Measure::Holes | Measure::Pieces => dimension == 3 && base == 2,99 }100 }101}102103#[cfg(test)]104mod tests {105 use super::*;106 #[test]107 fn every_slug_parses_back() {108 for measure in Measure::ALL {109 assert_eq!(Measure::parse(measure.slug()).unwrap(), measure);110 }111 assert!(Measure::parse("area").is_err());112 }113 #[test]114 fn the_plane_and_the_cube_read_their_measures() {115 let plane: Vec<Measure> = Measure::ALL116 .into_iter()117 .filter(|m| m.applies(2, 2))118 .collect();119 assert_eq!(plane.len(), 8);120 let cube: Vec<Measure> = Measure::ALL121 .into_iter()122 .filter(|m| m.applies(3, 2))123 .collect();124 assert_eq!(cube.len(), 12);125 assert_eq!(Measure::ALL.iter().filter(|m| m.applies(1, 5)).count(), 3);126 assert!(!Measure::Triangles.applies(3, 3));127 assert_eq!(Measure::Euler.cost(), Cost::Grid);128 }129}