Skip to main content

use_regular_polytope/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use use_schlafli::SchlafliSymbol;
5
6/// A regular polygon descriptor.
7#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct RegularPolygon {
9    side_count: usize,
10    circumradius: f64,
11}
12
13impl RegularPolygon {
14    /// Creates a regular polygon with at least three sides and positive finite circumradius.
15    #[must_use]
16    pub const fn new(side_count: usize, circumradius: f64) -> Option<Self> {
17        if side_count >= 3 && circumradius.is_finite() && circumradius > 0.0 {
18            Some(Self {
19                side_count,
20                circumradius,
21            })
22        } else {
23            None
24        }
25    }
26
27    /// Returns the side count.
28    #[must_use]
29    pub const fn side_count(self) -> usize {
30        self.side_count
31    }
32
33    /// Returns the circumradius.
34    #[must_use]
35    pub const fn circumradius(self) -> f64 {
36        self.circumradius
37    }
38
39    /// Returns the Schlafli symbol `{p}`.
40    #[must_use]
41    pub fn schlafli_symbol(self) -> SchlafliSymbol {
42        SchlafliSymbol::polygon(self.side_count).expect("regular polygon side count is valid")
43    }
44}
45
46/// Two-dimensional regular polytopes are regular polygons.
47pub type RegularPolytope2 = RegularPolygon;
48
49/// The five Platonic solids.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum PlatonicSolid {
52    /// Four triangular faces.
53    Tetrahedron,
54    /// Six square faces.
55    Cube,
56    /// Eight triangular faces.
57    Octahedron,
58    /// Twelve pentagonal faces.
59    Dodecahedron,
60    /// Twenty triangular faces.
61    Icosahedron,
62}
63
64impl PlatonicSolid {
65    /// Returns the common lowercase name.
66    #[must_use]
67    pub const fn name(self) -> &'static str {
68        match self {
69            Self::Tetrahedron => "tetrahedron",
70            Self::Cube => "cube",
71            Self::Octahedron => "octahedron",
72            Self::Dodecahedron => "dodecahedron",
73            Self::Icosahedron => "icosahedron",
74        }
75    }
76
77    /// Returns the number of faces.
78    #[must_use]
79    pub const fn face_count(self) -> usize {
80        match self {
81            Self::Tetrahedron => 4,
82            Self::Cube => 6,
83            Self::Octahedron => 8,
84            Self::Dodecahedron => 12,
85            Self::Icosahedron => 20,
86        }
87    }
88
89    /// Returns the number of edges.
90    #[must_use]
91    pub const fn edge_count(self) -> usize {
92        match self {
93            Self::Tetrahedron => 6,
94            Self::Cube => 12,
95            Self::Octahedron => 12,
96            Self::Dodecahedron => 30,
97            Self::Icosahedron => 30,
98        }
99    }
100
101    /// Returns the number of vertices.
102    #[must_use]
103    pub const fn vertex_count(self) -> usize {
104        match self {
105            Self::Tetrahedron => 4,
106            Self::Cube => 8,
107            Self::Octahedron => 6,
108            Self::Dodecahedron => 20,
109            Self::Icosahedron => 12,
110        }
111    }
112
113    /// Returns the Schlafli symbol.
114    #[must_use]
115    pub fn schlafli_symbol(self) -> SchlafliSymbol {
116        let (p, q) = match self {
117            Self::Tetrahedron => (3, 3),
118            Self::Cube => (4, 3),
119            Self::Octahedron => (3, 4),
120            Self::Dodecahedron => (5, 3),
121            Self::Icosahedron => (3, 5),
122        };
123
124        SchlafliSymbol::polyhedron(p, q).expect("Platonic solid Schlafli entries are valid")
125    }
126}
127
128/// Three-dimensional regular polytopes are the Platonic solids.
129pub type RegularPolytope3 = PlatonicSolid;
130
131/// Marker for the 5-cell.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub struct FiveCell;
134
135/// Marker for the tesseract.
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub struct Tesseract;
138
139/// Marker for the 16-cell.
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub struct SixteenCell;
142
143/// Marker for the 24-cell.
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub struct TwentyFourCell;
146
147/// Marker for the 600-cell.
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct SixHundredCell;
150
151/// Marker for the 120-cell.
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct OneHundredTwentyCell;
154
155/// The six convex regular four-dimensional polytopes.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum RegularPolytope4 {
158    /// The 5-cell.
159    FiveCell,
160    /// The tesseract.
161    Tesseract,
162    /// The 16-cell.
163    SixteenCell,
164    /// The 24-cell.
165    TwentyFourCell,
166    /// The 600-cell.
167    SixHundredCell,
168    /// The 120-cell.
169    OneHundredTwentyCell,
170}
171
172impl RegularPolytope4 {
173    /// Returns the common lowercase name.
174    #[must_use]
175    pub const fn name(self) -> &'static str {
176        match self {
177            Self::FiveCell => "5-cell",
178            Self::Tesseract => "tesseract",
179            Self::SixteenCell => "16-cell",
180            Self::TwentyFourCell => "24-cell",
181            Self::SixHundredCell => "600-cell",
182            Self::OneHundredTwentyCell => "120-cell",
183        }
184    }
185
186    /// Returns the vertex count.
187    #[must_use]
188    pub const fn vertex_count(self) -> usize {
189        match self {
190            Self::FiveCell => 5,
191            Self::Tesseract => 16,
192            Self::SixteenCell => 8,
193            Self::TwentyFourCell => 24,
194            Self::SixHundredCell => 120,
195            Self::OneHundredTwentyCell => 600,
196        }
197    }
198
199    /// Returns the edge count.
200    #[must_use]
201    pub const fn edge_count(self) -> usize {
202        match self {
203            Self::FiveCell => 10,
204            Self::Tesseract => 32,
205            Self::SixteenCell => 24,
206            Self::TwentyFourCell => 96,
207            Self::SixHundredCell => 720,
208            Self::OneHundredTwentyCell => 1200,
209        }
210    }
211
212    /// Returns the two-dimensional face count.
213    #[must_use]
214    pub const fn face_count(self) -> usize {
215        match self {
216            Self::FiveCell => 10,
217            Self::Tesseract => 24,
218            Self::SixteenCell => 32,
219            Self::TwentyFourCell => 96,
220            Self::SixHundredCell => 1200,
221            Self::OneHundredTwentyCell => 720,
222        }
223    }
224
225    /// Returns the three-dimensional cell count.
226    #[must_use]
227    pub const fn cell_count(self) -> usize {
228        match self {
229            Self::FiveCell => 5,
230            Self::Tesseract => 8,
231            Self::SixteenCell => 16,
232            Self::TwentyFourCell => 24,
233            Self::SixHundredCell => 600,
234            Self::OneHundredTwentyCell => 120,
235        }
236    }
237
238    /// Returns the Schlafli symbol.
239    #[must_use]
240    pub fn schlafli_symbol(self) -> SchlafliSymbol {
241        let (p, q, r) = match self {
242            Self::FiveCell => (3, 3, 3),
243            Self::Tesseract => (4, 3, 3),
244            Self::SixteenCell => (3, 3, 4),
245            Self::TwentyFourCell => (3, 4, 3),
246            Self::SixHundredCell => (3, 3, 5),
247            Self::OneHundredTwentyCell => (5, 3, 3),
248        };
249
250        SchlafliSymbol::polychoron(p, q, r).expect("regular 4-polytope entries are valid")
251    }
252}
253
254impl FiveCell {
255    /// Returns the 5-cell family enum value.
256    #[must_use]
257    pub const fn family() -> RegularPolytope4 {
258        RegularPolytope4::FiveCell
259    }
260}
261
262impl Tesseract {
263    /// Returns the tesseract family enum value.
264    #[must_use]
265    pub const fn family() -> RegularPolytope4 {
266        RegularPolytope4::Tesseract
267    }
268}
269
270impl SixteenCell {
271    /// Returns the 16-cell family enum value.
272    #[must_use]
273    pub const fn family() -> RegularPolytope4 {
274        RegularPolytope4::SixteenCell
275    }
276}
277
278impl TwentyFourCell {
279    /// Returns the 24-cell family enum value.
280    #[must_use]
281    pub const fn family() -> RegularPolytope4 {
282        RegularPolytope4::TwentyFourCell
283    }
284}
285
286impl SixHundredCell {
287    /// Returns the 600-cell family enum value.
288    #[must_use]
289    pub const fn family() -> RegularPolytope4 {
290        RegularPolytope4::SixHundredCell
291    }
292}
293
294impl OneHundredTwentyCell {
295    /// Returns the 120-cell family enum value.
296    #[must_use]
297    pub const fn family() -> RegularPolytope4 {
298        RegularPolytope4::OneHundredTwentyCell
299    }
300}
301
302/// A regular polytope descriptor across common dimensions.
303#[derive(Debug, Clone, Copy, PartialEq)]
304pub enum RegularPolytope {
305    /// A regular polygon.
306    Polygon(RegularPolytope2),
307    /// A Platonic solid.
308    PlatonicSolid(RegularPolytope3),
309    /// A convex regular four-dimensional polytope.
310    Polytope4(RegularPolytope4),
311}
312
313impl RegularPolytope {
314    /// Returns the polytope dimension.
315    #[must_use]
316    pub const fn dimension(self) -> usize {
317        match self {
318            Self::Polygon(_) => 2,
319            Self::PlatonicSolid(_) => 3,
320            Self::Polytope4(_) => 4,
321        }
322    }
323
324    /// Returns the Schlafli symbol.
325    #[must_use]
326    pub fn schlafli_symbol(self) -> SchlafliSymbol {
327        match self {
328            Self::Polygon(polygon) => polygon.schlafli_symbol(),
329            Self::PlatonicSolid(solid) => solid.schlafli_symbol(),
330            Self::Polytope4(polytope) => polytope.schlafli_symbol(),
331        }
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::{PlatonicSolid, RegularPolygon, RegularPolytope, RegularPolytope4};
338
339    #[test]
340    fn stores_regular_polygon_data() {
341        let polygon = RegularPolygon::new(6, 2.0).expect("valid polygon");
342
343        assert_eq!(polygon.side_count(), 6);
344        assert_eq!(polygon.circumradius(), 2.0);
345        assert_eq!(polygon.schlafli_symbol().to_string(), "{6}");
346        assert_eq!(RegularPolygon::new(2, 2.0), None);
347    }
348
349    #[test]
350    fn exposes_platonic_solid_counts() {
351        assert_eq!(PlatonicSolid::Icosahedron.face_count(), 20);
352        assert_eq!(PlatonicSolid::Cube.edge_count(), 12);
353        assert_eq!(PlatonicSolid::Dodecahedron.vertex_count(), 20);
354        assert_eq!(
355            PlatonicSolid::Icosahedron.schlafli_symbol().to_string(),
356            "{3, 5}"
357        );
358    }
359
360    #[test]
361    fn exposes_four_dimensional_regular_polytope_metadata() {
362        assert_eq!(RegularPolytope4::TwentyFourCell.name(), "24-cell");
363        assert_eq!(RegularPolytope4::TwentyFourCell.vertex_count(), 24);
364        assert_eq!(RegularPolytope4::SixHundredCell.cell_count(), 600);
365        assert_eq!(RegularPolytope4::OneHundredTwentyCell.face_count(), 720);
366        assert_eq!(
367            RegularPolytope4::TwentyFourCell
368                .schlafli_symbol()
369                .to_string(),
370            "{3, 4, 3}"
371        );
372    }
373
374    #[test]
375    fn wraps_regular_polytope_families() {
376        let polytope = RegularPolytope::Polytope4(RegularPolytope4::Tesseract);
377
378        assert_eq!(polytope.dimension(), 4);
379        assert_eq!(polytope.schlafli_symbol().to_string(), "{4, 3, 3}");
380    }
381}