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}