Skip to main content

rust_igraph/algorithms/constructors/
famous.rs

1//! Famous named graphs (ALGO-CN-020).
2//!
3//! Counterpart of `igraph_famous()` in
4//! `references/igraph/src/constructors/famous.c:423-498`. Given a
5//! case-insensitive name, returns the canonical labelled copy of one of
6//! 31 small graphs that appear repeatedly in graph-theory literature
7//! (Bull, Chvátal, Coxeter, Cubical, Diamond, Dodecahedron, Folkman,
8//! Franklin, Frucht, Grötzsch, Heawood, Herschel, House, `HouseX`,
9//! Icosahedron, Krackhardt Kite, Levi, `McGee`, Meredith, Noperfectmatching,
10//! Nonline, Octahedron, Petersen, Robertson, Smallestcyclicgroup,
11//! Tetrahedron, Thomassen, Tutte, Uniquely3colorable, Walther, Zachary).
12//!
13//! Aliases follow upstream: `dodecahedral ≡ dodecahedron`,
14//! `grotzsch ≡ groetzsch`, `icosahedral ≡ icosahedron`,
15//! `octahedral ≡ octahedron`, `tetrahedral ≡ tetrahedron`. Every famous
16//! graph is undirected.
17//!
18//! The edge tables are transliterated byte-for-byte from
19//! `famous.c:26-249` — each row holds `[vcount, ecount, directed_flag,
20//! edge0_a, edge0_b, …]` so the data layout matches `igraph_i_famous()`
21//! and the conformance test compares the raw ordered edge list.
22
23use crate::core::{Graph, IgraphError, IgraphResult};
24
25/// Build a famous named graph by name (case-insensitive).
26///
27/// All 31 graphs ship as undirected. `Bull`, `Chvatal`, `Coxeter`,
28/// `Cubical`, `Diamond`, `Dodecahedral`/`Dodecahedron`, `Folkman`,
29/// `Franklin`, `Frucht`, `Grotzsch`/`Groetzsch`, `Heawood`, `Herschel`,
30/// `House`, `HouseX`, `Icosahedral`/`Icosahedron`, `Krackhardt_Kite`,
31/// `Levi`, `McGee`, `Meredith`, `Noperfectmatching`, `Nonline`,
32/// `Octahedral`/`Octahedron`, `Petersen`, `Robertson`,
33/// `Smallestcyclicgroup`, `Tetrahedral`/`Tetrahedron`, `Thomassen`,
34/// `Tutte`, `Uniquely3colorable`, `Walther`, `Zachary`.
35///
36/// # Errors
37///
38/// * [`IgraphError::InvalidArgument`] — `name` does not match any
39///   supported graph (case-insensitive, ASCII).
40///
41/// # Examples
42///
43/// ```
44/// use rust_igraph::famous;
45///
46/// let petersen = famous("Petersen").unwrap();
47/// assert_eq!(petersen.vcount(), 10);
48/// assert_eq!(petersen.ecount(), 15);
49///
50/// // Case-insensitive.
51/// let zachary = famous("zachary").unwrap();
52/// assert_eq!(zachary.vcount(), 34);
53/// assert_eq!(zachary.ecount(), 78);
54/// ```
55pub fn famous(name: &str) -> IgraphResult<Graph> {
56    for &(candidate, data) in TABLE {
57        if name.eq_ignore_ascii_case(candidate) {
58            return materialize(data);
59        }
60    }
61    Err(IgraphError::InvalidArgument(format!(
62        "famous: '{name}' is not a known graph; see famous_names() for the list"
63    )))
64}
65
66/// Canonical (lowercase) names of every famous graph supported by
67/// [`famous`]. Aliases are not duplicated here — each graph appears once
68/// under its primary name. `famous` itself still accepts every alias.
69///
70/// ```
71/// use rust_igraph::famous_names;
72///
73/// let names = famous_names();
74/// assert!(names.contains(&"petersen"));
75/// assert!(names.contains(&"zachary"));
76/// ```
77#[must_use]
78pub fn famous_names() -> &'static [&'static str] {
79    &[
80        "bull",
81        "chvatal",
82        "coxeter",
83        "cubical",
84        "diamond",
85        "dodecahedron",
86        "folkman",
87        "franklin",
88        "frucht",
89        "grotzsch",
90        "heawood",
91        "herschel",
92        "house",
93        "housex",
94        "icosahedron",
95        "krackhardt_kite",
96        "levi",
97        "mcgee",
98        "meredith",
99        "noperfectmatching",
100        "nonline",
101        "octahedron",
102        "petersen",
103        "robertson",
104        "smallestcyclicgroup",
105        "tetrahedron",
106        "thomassen",
107        "tutte",
108        "uniquely3colorable",
109        "walther",
110        "zachary",
111    ]
112}
113
114fn materialize(data: &[u32]) -> IgraphResult<Graph> {
115    let vcount = data[0];
116    let ecount = data[1] as usize;
117    let directed = data[2] != 0;
118
119    let expected_len =
120        3usize
121            .checked_add(ecount.checked_mul(2).ok_or_else(|| {
122                IgraphError::InvalidArgument("famous: edge buffer overflow".into())
123            })?)
124            .ok_or_else(|| IgraphError::InvalidArgument("famous: edge buffer overflow".into()))?;
125    if data.len() != expected_len {
126        return Err(IgraphError::InvalidArgument(format!(
127            "famous: data length mismatch (expected {expected_len}, got {})",
128            data.len()
129        )));
130    }
131
132    let mut g = Graph::new(vcount, directed)?;
133    let edges: Vec<(u32, u32)> = data[3..]
134        .chunks_exact(2)
135        .map(|pair| (pair[0], pair[1]))
136        .collect();
137    g.add_edges(edges)?;
138    Ok(g)
139}
140
141// Lookup table: every accepted name (canonical + aliases) maps to its
142// edge data array. Sorted by canonical name; aliases sit next to the
143// graph they alias.
144#[allow(clippy::type_complexity)]
145const TABLE: &[(&str, &[u32])] = &[
146    ("bull", DATA_BULL),
147    ("chvatal", DATA_CHVATAL),
148    ("coxeter", DATA_COXETER),
149    ("cubical", DATA_CUBICAL),
150    ("diamond", DATA_DIAMOND),
151    ("dodecahedral", DATA_DODECAHEDRON),
152    ("dodecahedron", DATA_DODECAHEDRON),
153    ("folkman", DATA_FOLKMAN),
154    ("franklin", DATA_FRANKLIN),
155    ("frucht", DATA_FRUCHT),
156    ("groetzsch", DATA_GROTZSCH),
157    ("grotzsch", DATA_GROTZSCH),
158    ("heawood", DATA_HEAWOOD),
159    ("herschel", DATA_HERSCHEL),
160    ("house", DATA_HOUSE),
161    ("housex", DATA_HOUSEX),
162    ("icosahedral", DATA_ICOSAHEDRON),
163    ("icosahedron", DATA_ICOSAHEDRON),
164    ("krackhardt_kite", DATA_KRACKHARDT_KITE),
165    ("levi", DATA_LEVI),
166    ("mcgee", DATA_MCGEE),
167    ("meredith", DATA_MEREDITH),
168    ("noperfectmatching", DATA_NOPERFECTMATCHING),
169    ("nonline", DATA_NONLINE),
170    ("octahedral", DATA_OCTAHEDRON),
171    ("octahedron", DATA_OCTAHEDRON),
172    ("petersen", DATA_PETERSEN),
173    ("robertson", DATA_ROBERTSON),
174    ("smallestcyclicgroup", DATA_SMALLESTCYCLICGROUP),
175    ("tetrahedral", DATA_TETRAHEDRON),
176    ("tetrahedron", DATA_TETRAHEDRON),
177    ("thomassen", DATA_THOMASSEN),
178    ("tutte", DATA_TUTTE),
179    ("uniquely3colorable", DATA_UNIQUELY3COLORABLE),
180    ("walther", DATA_WALTHER),
181    ("zachary", DATA_ZACHARY),
182];
183
184// =============================================================================
185// Edge tables — transliterated byte-for-byte from
186// references/igraph/src/constructors/famous.c:26-249.
187// Each row holds [vcount, ecount, directed_flag, edge0_a, edge0_b, …].
188// =============================================================================
189
190#[rustfmt::skip]
191const DATA_BULL: &[u32] = &[
192    5, 5, 0,
193    0, 1, 0, 2, 1, 2, 1, 3, 2, 4,
194];
195
196#[rustfmt::skip]
197const DATA_CHVATAL: &[u32] = &[
198    12, 24, 0,
199    5, 6, 6, 7, 7, 8, 8, 9, 5, 9, 4, 5, 4, 8, 2, 8, 2, 6, 0, 6, 0, 9, 3, 9, 3, 7,
200    1, 7, 1, 5, 1, 10, 4, 10, 4, 11, 2, 11, 0, 10, 0, 11, 3, 11, 3, 10, 1, 2,
201];
202
203#[rustfmt::skip]
204const DATA_COXETER: &[u32] = &[
205    28, 42, 0,
206    0, 1, 0, 2, 0, 7, 1, 4, 1, 13, 2, 3, 2, 8, 3, 6, 3, 9, 4, 5, 4, 12, 5, 6, 5,
207    11, 6, 10, 7, 19, 7, 24, 8, 20, 8, 23, 9, 14, 9, 22, 10, 15, 10, 21, 11, 16,
208    11, 27, 12, 17, 12, 26, 13, 18, 13, 25, 14, 17, 14, 18, 15, 18, 15, 19, 16, 19,
209    16, 20, 17, 20, 21, 23, 21, 26, 22, 24, 22, 27, 23, 25, 24, 26, 25, 27,
210];
211
212#[rustfmt::skip]
213const DATA_CUBICAL: &[u32] = &[
214    8, 12, 0,
215    0, 1, 1, 2, 2, 3, 0, 3, 4, 5, 5, 6, 6, 7, 4, 7, 0, 4, 1, 5, 2, 6, 3, 7,
216];
217
218#[rustfmt::skip]
219const DATA_DIAMOND: &[u32] = &[
220    4, 5, 0,
221    0, 1, 0, 2, 1, 2, 1, 3, 2, 3,
222];
223
224#[rustfmt::skip]
225const DATA_DODECAHEDRON: &[u32] = &[
226    20, 30, 0,
227    0, 1, 0, 4, 0, 5, 1, 2, 1, 6, 2, 3, 2, 7, 3, 4, 3, 8, 4, 9, 5, 10, 5, 11, 6,
228    10, 6, 14, 7, 13, 7, 14, 8, 12, 8, 13, 9, 11, 9, 12, 10, 15, 11, 16, 12, 17,
229    13, 18, 14, 19, 15, 16, 15, 19, 16, 17, 17, 18, 18, 19,
230];
231
232#[rustfmt::skip]
233const DATA_FOLKMAN: &[u32] = &[
234    20, 40, 0,
235    0, 5, 0, 8, 0, 10, 0, 13, 1, 7, 1, 9, 1, 12, 1, 14, 2, 6, 2, 8, 2, 11, 2, 13,
236    3, 5, 3, 7, 3, 10, 3, 12, 4, 6, 4, 9, 4, 11, 4, 14, 5, 15, 5, 19, 6, 15, 6, 16,
237    7, 16, 7, 17, 8, 17, 8, 18, 9, 18, 9, 19, 10, 15, 10, 19, 11, 15, 11, 16, 12,
238    16, 12, 17, 13, 17, 13, 18, 14, 18, 14, 19,
239];
240
241#[rustfmt::skip]
242const DATA_FRANKLIN: &[u32] = &[
243    12, 18, 0,
244    0, 1, 0, 2, 0, 6, 1, 3, 1, 7, 2, 4, 2, 10, 3, 5, 3, 11, 4, 5, 4, 6, 5, 7, 6, 8,
245    7, 9, 8, 9, 8, 11, 9, 10, 10, 11,
246];
247
248#[rustfmt::skip]
249const DATA_FRUCHT: &[u32] = &[
250    12, 18, 0,
251    0, 1, 0, 2, 0, 11, 1, 3, 1, 6, 2, 5, 2, 10, 3, 4, 3, 6, 4, 8, 4, 11, 5, 9, 5,
252    10, 6, 7, 7, 8, 7, 9, 8, 9, 10, 11,
253];
254
255#[rustfmt::skip]
256const DATA_GROTZSCH: &[u32] = &[
257    11, 20, 0,
258    0, 1, 0, 2, 0, 7, 0, 10, 1, 3, 1, 6, 1, 9, 2, 4, 2, 6, 2, 8, 3, 4, 3, 8, 3, 10,
259    4, 7, 4, 9, 5, 6, 5, 7, 5, 8, 5, 9, 5, 10,
260];
261
262#[rustfmt::skip]
263const DATA_HEAWOOD: &[u32] = &[
264    14, 21, 0,
265    0, 1, 0, 5, 0, 13, 1, 2, 1, 10, 2, 3, 2, 7, 3, 4, 3, 12, 4, 5, 4, 9, 5, 6, 6,
266    7, 6, 11, 7, 8, 8, 9, 8, 13, 9, 10, 10, 11, 11, 12, 12, 13,
267];
268
269#[rustfmt::skip]
270const DATA_HERSCHEL: &[u32] = &[
271    11, 18, 0,
272    0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 6, 1, 7, 2, 10, 3, 9, 4, 8, 4, 9, 5, 8,
273    5, 10, 6, 8, 6, 9, 7, 8, 7, 10,
274];
275
276#[rustfmt::skip]
277const DATA_HOUSE: &[u32] = &[
278    5, 6, 0,
279    0, 1, 0, 2, 1, 3, 2, 3, 2, 4, 3, 4,
280];
281
282#[rustfmt::skip]
283const DATA_HOUSEX: &[u32] = &[
284    5, 8, 0,
285    0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, 2, 4, 3, 4,
286];
287
288#[rustfmt::skip]
289const DATA_ICOSAHEDRON: &[u32] = &[
290    12, 30, 0,
291    0, 1, 0, 2, 0, 3, 0, 4, 0, 8, 1, 2, 1, 6, 1, 7, 1, 8, 2, 4, 2, 5, 2, 6, 3, 4,
292    3, 8, 3, 9, 3, 11, 4, 5, 4, 11, 5, 6, 5, 10, 5, 11, 6, 7, 6, 10, 7, 8, 7, 9, 7,
293    10, 8, 9, 9, 10, 9, 11, 10, 11,
294];
295
296#[rustfmt::skip]
297const DATA_KRACKHARDT_KITE: &[u32] = &[
298    10, 18, 0,
299    0, 1, 0, 2, 0, 3, 0, 5, 1, 3, 1, 4, 1, 6, 2, 3, 2, 5, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, 5, 7, 6, 7, 7, 8, 8, 9,
300];
301
302#[rustfmt::skip]
303const DATA_LEVI: &[u32] = &[
304    30, 45, 0,
305    0, 1, 0, 7, 0, 29, 1, 2, 1, 24, 2, 3, 2, 11, 3, 4, 3, 16, 4, 5, 4, 21, 5, 6, 5,
306    26, 6, 7, 6, 13, 7, 8, 8, 9, 8, 17, 9, 10, 9, 22, 10, 11, 10, 27, 11, 12, 12,
307    13, 12, 19, 13, 14, 14, 15, 14, 23, 15, 16, 15, 28, 16, 17, 17, 18, 18, 19, 18,
308    25, 19, 20, 20, 21, 20, 29, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
309    28, 28, 29,
310];
311
312#[rustfmt::skip]
313const DATA_MCGEE: &[u32] = &[
314    24, 36, 0,
315    0, 1, 0, 7, 0, 23, 1, 2, 1, 18, 2, 3, 2, 14, 3, 4, 3, 10, 4, 5, 4, 21, 5, 6, 5,
316    17, 6, 7, 6, 13, 7, 8, 8, 9, 8, 20, 9, 10, 9, 16, 10, 11, 11, 12, 11, 23, 12,
317    13, 12, 19, 13, 14, 14, 15, 15, 16, 15, 22, 16, 17, 17, 18, 18, 19, 19, 20, 20,
318    21, 21, 22, 22, 23,
319];
320
321#[rustfmt::skip]
322const DATA_MEREDITH: &[u32] = &[
323    70, 140, 0,
324    0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 7, 11,
325    7, 12, 7, 13, 8, 11, 8, 12, 8, 13, 9, 11, 9, 12, 9, 13, 10, 11, 10, 12, 10, 13,
326    14, 18, 14, 19, 14, 20, 15, 18, 15, 19, 15, 20, 16, 18, 16, 19, 16, 20, 17, 18,
327    17, 19, 17, 20, 21, 25, 21, 26, 21, 27, 22, 25, 22, 26, 22, 27, 23, 25, 23, 26,
328    23, 27, 24, 25, 24, 26, 24, 27, 28, 32, 28, 33, 28, 34, 29, 32, 29, 33, 29, 34,
329    30, 32, 30, 33, 30, 34, 31, 32, 31, 33, 31, 34, 35, 39, 35, 40, 35, 41, 36, 39,
330    36, 40, 36, 41, 37, 39, 37, 40, 37, 41, 38, 39, 38, 40, 38, 41, 42, 46, 42, 47,
331    42, 48, 43, 46, 43, 47, 43, 48, 44, 46, 44, 47, 44, 48, 45, 46, 45, 47, 45, 48,
332    49, 53, 49, 54, 49, 55, 50, 53, 50, 54, 50, 55, 51, 53, 51, 54, 51, 55, 52, 53,
333    52, 54, 52, 55, 56, 60, 56, 61, 56, 62, 57, 60, 57, 61, 57, 62, 58, 60, 58, 61,
334    58, 62, 59, 60, 59, 61, 59, 62, 63, 67, 63, 68, 63, 69, 64, 67, 64, 68, 64, 69,
335    65, 67, 65, 68, 65, 69, 66, 67, 66, 68, 66, 69, 2, 50, 1, 51, 9, 57, 8, 58, 16,
336    64, 15, 65, 23, 36, 22, 37, 30, 43, 29, 44, 3, 21, 7, 24, 14, 31, 0, 17, 10,
337    28, 38, 42, 35, 66, 59, 63, 52, 56, 45, 49,
338];
339
340#[rustfmt::skip]
341const DATA_NOPERFECTMATCHING: &[u32] = &[
342    16, 27, 0,
343    0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, 2, 4, 3, 4, 4, 5, 5, 6, 5, 7, 6, 12, 6, 13,
344    7, 8, 7, 9, 8, 9, 8, 10, 8, 11, 9, 10, 9, 11, 10, 11, 12, 13, 12, 14, 12, 15,
345    13, 14, 13, 15, 14, 15,
346];
347
348#[rustfmt::skip]
349const DATA_NONLINE: &[u32] = &[
350    50, 72, 0,
351    0, 1, 0, 2, 0, 3, 4, 6, 4, 7, 5, 6, 5, 7, 6, 7, 7, 8, 9, 11, 9, 12, 9, 13, 10,
352    11, 10, 12, 10, 13, 11, 12, 11, 13, 12, 13, 14, 15, 15, 16, 15, 17, 16, 17, 16,
353    18, 17, 18, 18, 19, 20, 21, 20, 22, 20, 23, 21, 22, 21, 23, 21, 24, 22, 23, 22,
354    24, 24, 25, 26, 27, 26, 28, 26, 29, 27, 28, 27, 29, 27, 30, 27, 31, 28, 29, 28,
355    30, 28, 31, 30, 31, 32, 34, 32, 35, 32, 36, 33, 34, 33, 35, 33, 37, 34, 35, 36,
356    37, 38, 39, 38, 40, 38, 43, 39, 40, 39, 41, 39, 42, 39, 43, 40, 41, 41, 42, 42,
357    43, 44, 45, 44, 46, 45, 46, 45, 47, 46, 47, 46, 48, 47, 48, 47, 49, 48, 49,
358];
359
360#[rustfmt::skip]
361const DATA_OCTAHEDRON: &[u32] = &[
362    6, 12, 0,
363    0, 1, 0, 2, 1, 2, 3, 4, 3, 5, 4, 5, 0, 3, 0, 5, 1, 3, 1, 4, 2, 4, 2, 5,
364];
365
366#[rustfmt::skip]
367const DATA_PETERSEN: &[u32] = &[
368    10, 15, 0,
369    0, 1, 0, 4, 0, 5, 1, 2, 1, 6, 2, 3, 2, 7, 3, 4, 3, 8, 4, 9, 5, 7, 5, 8, 6, 8, 6, 9, 7, 9,
370];
371
372#[rustfmt::skip]
373const DATA_ROBERTSON: &[u32] = &[
374    19, 38, 0,
375    0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12,
376    12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 0, 18, 0, 4, 4, 9, 9, 13, 13,
377    17, 2, 17, 2, 6, 6, 10, 10, 15, 0, 15, 1, 8, 8, 16, 5, 16, 5, 12, 1, 12, 7, 18,
378    7, 14, 3, 14, 3, 11, 11, 18,
379];
380
381#[rustfmt::skip]
382const DATA_SMALLESTCYCLICGROUP: &[u32] = &[
383    9, 15, 0,
384    0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 7, 1, 8, 2, 5, 2, 6, 2, 7, 3, 8,
385    4, 5, 6, 7,
386];
387
388#[rustfmt::skip]
389const DATA_TETRAHEDRON: &[u32] = &[
390    4, 6, 0,
391    0, 3, 1, 3, 2, 3, 0, 1, 1, 2, 0, 2,
392];
393
394#[rustfmt::skip]
395const DATA_THOMASSEN: &[u32] = &[
396    34, 52, 0,
397    0, 2, 0, 3, 1, 3, 1, 4, 2, 4, 5, 7, 5, 8, 6, 8, 6, 9, 7, 9, 10, 12, 10, 13, 11,
398    13, 11, 14, 12, 14, 15, 17, 15, 18, 16, 18, 16, 19, 17, 19, 9, 19, 4, 14, 24,
399    25, 25, 26, 20, 26, 20, 21, 21, 22, 22, 23, 23, 27, 27, 28, 28, 29, 29, 30, 30,
400    31, 31, 32, 32, 33, 24, 33, 5, 24, 6, 25, 7, 26, 8, 20, 0, 20, 1, 21, 2, 22, 3,
401    23, 10, 27, 11, 28, 12, 29, 13, 30, 15, 30, 16, 31, 17, 32, 18, 33,
402];
403
404#[rustfmt::skip]
405const DATA_TUTTE: &[u32] = &[
406    46, 69, 0,
407    0, 10, 0, 11, 0, 12, 1, 2, 1, 7, 1, 19, 2, 3, 2, 41, 3, 4, 3, 27, 4, 5, 4, 33,
408    5, 6, 5, 45, 6, 9, 6, 29, 7, 8, 7, 21, 8, 9, 8, 22, 9, 24, 10, 13, 10, 14, 11,
409    26, 11, 28, 12, 30, 12, 31, 13, 15, 13, 21, 14, 15, 14, 18, 15, 16, 16, 17, 16,
410    20, 17, 18, 17, 23, 18, 24, 19, 25, 19, 40, 20, 21, 20, 22, 22, 23, 23, 24, 25,
411    26, 25, 38, 26, 34, 27, 28, 27, 39, 28, 34, 29, 30, 29, 44, 30, 35, 31, 32, 31,
412    35, 32, 33, 32, 42, 33, 43, 34, 36, 35, 37, 36, 38, 36, 39, 37, 42, 37, 44, 38,
413    40, 39, 41, 40, 41, 42, 43, 43, 45, 44, 45,
414];
415
416#[rustfmt::skip]
417const DATA_UNIQUELY3COLORABLE: &[u32] = &[
418    12, 22, 0,
419    0, 1, 0, 3, 0, 6, 0, 8, 1, 4, 1, 7, 1, 9, 2, 3, 2, 6, 2, 7, 2, 9, 2, 11, 3, 4,
420    3, 10, 4, 5, 4, 11, 5, 6, 5, 7, 5, 8, 5, 10, 8, 11, 9, 10,
421];
422
423#[rustfmt::skip]
424const DATA_WALTHER: &[u32] = &[
425    25, 31, 0,
426    0, 1, 1, 2, 1, 8, 2, 3, 2, 13, 3, 4, 3, 16, 4, 5, 5, 6, 5, 19, 6, 7, 6, 20, 7,
427    21, 8, 9, 8, 13, 9, 10, 9, 22, 10, 11, 10, 20, 11, 12, 13, 14, 14, 15, 14, 23,
428    15, 16, 15, 17, 17, 18, 18, 19, 18, 24, 20, 24, 22, 23, 23, 24,
429];
430
431#[rustfmt::skip]
432const DATA_ZACHARY: &[u32] = &[
433    34, 78, 0,
434    0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8,
435    0, 10, 0, 11, 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, 0, 31,
436    1, 2, 1, 3, 1, 7, 1, 13, 1, 17, 1, 19, 1, 21, 1, 30,
437    2, 3, 2, 7, 2, 27, 2, 28, 2, 32, 2, 9, 2, 8, 2, 13,
438    3, 7, 3, 12, 3, 13, 4, 6, 4, 10, 5, 6, 5, 10, 5, 16,
439    6, 16, 8, 30, 8, 32, 8, 33, 9, 33, 13, 33, 14, 32, 14, 33,
440    15, 32, 15, 33, 18, 32, 18, 33, 19, 33, 20, 32, 20, 33,
441    22, 32, 22, 33, 23, 25, 23, 27, 23, 32, 23, 33, 23, 29,
442    24, 25, 24, 27, 24, 31, 25, 31, 26, 29, 26, 33, 27, 33,
443    28, 31, 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, 31, 32, 31, 33,
444    32, 33,
445];
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450    use std::collections::BTreeSet;
451
452    fn canonical_edge_set(g: &Graph) -> BTreeSet<(u32, u32)> {
453        let mut s = BTreeSet::new();
454        for v in 0..g.vcount() {
455            for &u in &g.neighbors(v).expect("neighbors") {
456                if v <= u {
457                    s.insert((v, u));
458                } else {
459                    s.insert((u, v));
460                }
461            }
462        }
463        s
464    }
465
466    #[test]
467    fn unknown_name_errors() {
468        assert!(famous("nonexistent").is_err());
469        assert!(famous("").is_err());
470    }
471
472    #[test]
473    fn names_are_case_insensitive() {
474        let p1 = famous("PETERSEN").expect("upper");
475        let p2 = famous("Petersen").expect("title");
476        let p3 = famous("petersen").expect("lower");
477        let p4 = famous("PeTeRsEn").expect("mixed");
478        for p in [&p2, &p3, &p4] {
479            assert_eq!(p1.vcount(), p.vcount());
480            assert_eq!(p1.ecount(), p.ecount());
481            assert_eq!(canonical_edge_set(&p1), canonical_edge_set(p));
482        }
483    }
484
485    #[test]
486    fn aliases_match_primary() {
487        let pairs = [
488            ("dodecahedral", "dodecahedron"),
489            ("grotzsch", "groetzsch"),
490            ("icosahedral", "icosahedron"),
491            ("octahedral", "octahedron"),
492            ("tetrahedral", "tetrahedron"),
493        ];
494        for (a, b) in pairs {
495            let ga = famous(a).expect(a);
496            let gb = famous(b).expect(b);
497            assert_eq!(ga.vcount(), gb.vcount(), "{a} vs {b} vcount");
498            assert_eq!(ga.ecount(), gb.ecount(), "{a} vs {b} ecount");
499            assert_eq!(canonical_edge_set(&ga), canonical_edge_set(&gb));
500        }
501    }
502
503    #[test]
504    fn every_name_in_table_builds() {
505        for name in famous_names() {
506            let g = famous(name).unwrap_or_else(|_| panic!("famous({name}) failed"));
507            assert!(g.vcount() > 0, "{name} should have at least one vertex");
508            assert!(!g.is_directed(), "{name} should be undirected");
509        }
510    }
511
512    #[test]
513    fn vertex_and_edge_counts_match_data_table() {
514        // Spot-checks against the famous.c data header rows.
515        let cases = [
516            ("bull", 5, 5),
517            ("chvatal", 12, 24),
518            ("coxeter", 28, 42),
519            ("cubical", 8, 12),
520            ("diamond", 4, 5),
521            ("dodecahedron", 20, 30),
522            ("folkman", 20, 40),
523            ("franklin", 12, 18),
524            ("frucht", 12, 18),
525            ("grotzsch", 11, 20),
526            ("heawood", 14, 21),
527            ("herschel", 11, 18),
528            ("house", 5, 6),
529            ("housex", 5, 8),
530            ("icosahedron", 12, 30),
531            ("krackhardt_kite", 10, 18),
532            ("levi", 30, 45),
533            ("mcgee", 24, 36),
534            ("meredith", 70, 140),
535            ("noperfectmatching", 16, 27),
536            ("nonline", 50, 72),
537            ("octahedron", 6, 12),
538            ("petersen", 10, 15),
539            ("robertson", 19, 38),
540            ("smallestcyclicgroup", 9, 15),
541            ("tetrahedron", 4, 6),
542            ("thomassen", 34, 52),
543            ("tutte", 46, 69),
544            ("uniquely3colorable", 12, 22),
545            ("walther", 25, 31),
546            ("zachary", 34, 78),
547        ];
548        for (name, v, e) in cases {
549            let g = famous(name).expect(name);
550            assert_eq!(g.vcount(), v, "{name} vcount");
551            assert_eq!(g.ecount(), e, "{name} ecount");
552        }
553    }
554
555    #[test]
556    fn bull_has_expected_edges() {
557        let bull = famous("bull").expect("bull");
558        let want: BTreeSet<(u32, u32)> = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 4)]
559            .into_iter()
560            .collect();
561        assert_eq!(canonical_edge_set(&bull), want);
562    }
563
564    #[test]
565    fn tetrahedron_is_k4() {
566        // K_4 — every pair of distinct vertices connected exactly once.
567        let g = famous("tetrahedron").expect("t");
568        let want: BTreeSet<(u32, u32)> = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
569            .into_iter()
570            .collect();
571        assert_eq!(canonical_edge_set(&g), want);
572    }
573
574    #[test]
575    fn petersen_is_3_regular() {
576        let g = famous("petersen").expect("p");
577        for v in 0..g.vcount() {
578            assert_eq!(g.neighbors(v).unwrap().len(), 3, "vertex {v} degree");
579        }
580    }
581
582    #[test]
583    fn no_self_loops_in_undirected_famous() {
584        for name in famous_names() {
585            let g = famous(name).unwrap();
586            for v in 0..g.vcount() {
587                for &u in &g.neighbors(v).unwrap() {
588                    assert_ne!(v, u, "{name} has self-loop at {v}");
589                }
590            }
591        }
592    }
593
594    #[test]
595    fn famous_names_match_table_canonical_entries() {
596        // famous_names() should list exactly the canonical names (no aliases).
597        let names: BTreeSet<&str> = famous_names().iter().copied().collect();
598        assert_eq!(names.len(), famous_names().len(), "no duplicate names");
599        // Every name must be accepted by famous() itself.
600        for n in &names {
601            assert!(
602                famous(n).is_ok(),
603                "famous_names() entry '{n}' is not accepted"
604            );
605        }
606    }
607}