1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use use_schlafli::SchlafliSymbol;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct RegularPolygon {
9 side_count: usize,
10 circumradius: f64,
11}
12
13impl RegularPolygon {
14 #[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 #[must_use]
29 pub const fn side_count(self) -> usize {
30 self.side_count
31 }
32
33 #[must_use]
35 pub const fn circumradius(self) -> f64 {
36 self.circumradius
37 }
38
39 #[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
46pub type RegularPolytope2 = RegularPolygon;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum PlatonicSolid {
52 Tetrahedron,
54 Cube,
56 Octahedron,
58 Dodecahedron,
60 Icosahedron,
62}
63
64impl PlatonicSolid {
65 #[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 #[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 #[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 #[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 #[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
128pub type RegularPolytope3 = PlatonicSolid;
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub struct FiveCell;
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub struct Tesseract;
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub struct SixteenCell;
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub struct TwentyFourCell;
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct SixHundredCell;
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct OneHundredTwentyCell;
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum RegularPolytope4 {
158 FiveCell,
160 Tesseract,
162 SixteenCell,
164 TwentyFourCell,
166 SixHundredCell,
168 OneHundredTwentyCell,
170}
171
172impl RegularPolytope4 {
173 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
257 pub const fn family() -> RegularPolytope4 {
258 RegularPolytope4::FiveCell
259 }
260}
261
262impl Tesseract {
263 #[must_use]
265 pub const fn family() -> RegularPolytope4 {
266 RegularPolytope4::Tesseract
267 }
268}
269
270impl SixteenCell {
271 #[must_use]
273 pub const fn family() -> RegularPolytope4 {
274 RegularPolytope4::SixteenCell
275 }
276}
277
278impl TwentyFourCell {
279 #[must_use]
281 pub const fn family() -> RegularPolytope4 {
282 RegularPolytope4::TwentyFourCell
283 }
284}
285
286impl SixHundredCell {
287 #[must_use]
289 pub const fn family() -> RegularPolytope4 {
290 RegularPolytope4::SixHundredCell
291 }
292}
293
294impl OneHundredTwentyCell {
295 #[must_use]
297 pub const fn family() -> RegularPolytope4 {
298 RegularPolytope4::OneHundredTwentyCell
299 }
300}
301
302#[derive(Debug, Clone, Copy, PartialEq)]
304pub enum RegularPolytope {
305 Polygon(RegularPolytope2),
307 PlatonicSolid(RegularPolytope3),
309 Polytope4(RegularPolytope4),
311}
312
313impl RegularPolytope {
314 #[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 #[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}