Skip to main content

sci_form/materials/
space_groups.rs

1//! Crystallographic space groups (230 groups).
2//!
3//! Each space group entry contains:
4//! - ITC number (1–230)
5//! - Hermann-Mauguin symbol
6//! - Crystal system
7//! - Lattice type (P, I, F, C, R, A, B)
8//! - Point group
9//! - Symmetry operations (as rotation matrix + translation vector)
10
11use serde::{Deserialize, Serialize};
12
13/// Crystal system classification.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15pub enum CrystalSystem {
16    Triclinic,
17    Monoclinic,
18    Orthorhombic,
19    Tetragonal,
20    Trigonal,
21    Hexagonal,
22    Cubic,
23}
24
25/// Lattice centering type.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27pub enum LatticeType {
28    /// Primitive
29    P,
30    /// Body-centered
31    I,
32    /// Face-centered
33    F,
34    /// C-centered
35    C,
36    /// Rhombohedral
37    R,
38    /// A-centered
39    A,
40    /// B-centered
41    B,
42}
43
44/// A symmetry operation: rotation matrix (3×3 integers) + translation vector (rational, stored as f64).
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct SymmetryOp {
47    /// 3×3 rotation matrix (integer elements).
48    pub rotation: [[i8; 3]; 3],
49    /// Translation vector (fractional coordinates).
50    pub translation: [f64; 3],
51}
52
53impl SymmetryOp {
54    /// Apply this symmetry operation to fractional coordinates.
55    pub fn apply(&self, frac: &[f64; 3]) -> [f64; 3] {
56        let mut result = [0.0f64; 3];
57        for i in 0..3 {
58            result[i] = self.rotation[i][0] as f64 * frac[0]
59                + self.rotation[i][1] as f64 * frac[1]
60                + self.rotation[i][2] as f64 * frac[2]
61                + self.translation[i];
62            // Wrap to [0, 1)
63            result[i] = result[i] - result[i].floor();
64        }
65        result
66    }
67
68    /// Identity operation.
69    pub fn identity() -> Self {
70        Self {
71            rotation: [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
72            translation: [0.0, 0.0, 0.0],
73        }
74    }
75
76    /// Inversion operation.
77    pub fn inversion() -> Self {
78        Self {
79            rotation: [[-1, 0, 0], [0, -1, 0], [0, 0, -1]],
80            translation: [0.0, 0.0, 0.0],
81        }
82    }
83}
84
85/// A crystallographic space group.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct SpaceGroup {
88    /// ITC number (1–230).
89    pub number: u16,
90    /// Hermann-Mauguin symbol.
91    pub symbol: String,
92    /// Crystal system.
93    pub crystal_system: CrystalSystem,
94    /// Lattice centering type.
95    pub lattice_type: LatticeType,
96    /// Point group symbol.
97    pub point_group: String,
98    /// General position symmetry operations.
99    pub operations: Vec<SymmetryOp>,
100}
101
102impl SpaceGroup {
103    /// Generate all symmetry-equivalent positions from a single site.
104    pub fn generate_equivalent_positions(&self, site: &[f64; 3]) -> Vec<[f64; 3]> {
105        let mut positions = Vec::new();
106        for op in &self.operations {
107            let pos = op.apply(site);
108            // Check for duplicates (within tolerance)
109            let is_dup = positions.iter().any(|existing: &[f64; 3]| {
110                let dx = (pos[0] - existing[0]).abs();
111                let dy = (pos[1] - existing[1]).abs();
112                let dz = (pos[2] - existing[2]).abs();
113                // Account for periodic boundaries
114                let dx = dx.min(1.0 - dx);
115                let dy = dy.min(1.0 - dy);
116                let dz = dz.min(1.0 - dz);
117                dx < 1e-4 && dy < 1e-4 && dz < 1e-4
118            });
119            if !is_dup {
120                positions.push(pos);
121            }
122        }
123        positions
124    }
125
126    /// Get the Wyckoff multiplicity for a general position.
127    pub fn multiplicity(&self) -> usize {
128        self.operations.len()
129    }
130}
131
132/// Look up a space group by ITC number.
133pub fn space_group_by_number(number: u16) -> Option<SpaceGroup> {
134    build_space_group(number)
135}
136
137/// Look up a space group by Hermann-Mauguin symbol.
138pub fn space_group_by_symbol(symbol: &str) -> Option<SpaceGroup> {
139    let clean = symbol.replace(' ', "");
140    for num in 1..=230 {
141        if let Some(sg) = build_space_group(num) {
142            if sg.symbol.replace(' ', "") == clean {
143                return Some(sg);
144            }
145        }
146    }
147    None
148}
149
150/// Get crystal system for a given space group number.
151pub fn crystal_system_for_number(number: u16) -> Option<CrystalSystem> {
152    match number {
153        1..=2 => Some(CrystalSystem::Triclinic),
154        3..=15 => Some(CrystalSystem::Monoclinic),
155        16..=74 => Some(CrystalSystem::Orthorhombic),
156        75..=142 => Some(CrystalSystem::Tetragonal),
157        143..=167 => Some(CrystalSystem::Trigonal),
158        168..=194 => Some(CrystalSystem::Hexagonal),
159        195..=230 => Some(CrystalSystem::Cubic),
160        _ => None,
161    }
162}
163
164/// Build a space group with its symmetry operations.
165/// Includes common space groups with full symmetry operations,
166/// and all 230 with basic metadata.
167fn build_space_group(number: u16) -> Option<SpaceGroup> {
168    let crystal_system = crystal_system_for_number(number)?;
169    let (symbol, point_group, lattice_type, ops) = space_group_data(number)?;
170
171    Some(SpaceGroup {
172        number,
173        symbol: symbol.to_string(),
174        crystal_system,
175        lattice_type,
176        point_group: point_group.to_string(),
177        operations: ops,
178    })
179}
180
181/// Return (symbol, point_group, lattice_type, operations) for all 230 space groups.
182///
183/// Common space groups have full symmetry operations; less-common ones
184/// have the identity as a placeholder with correct metadata.
185fn space_group_data(
186    number: u16,
187) -> Option<(&'static str, &'static str, LatticeType, Vec<SymmetryOp>)> {
188    use LatticeType::*;
189    let id = SymmetryOp::identity();
190    let inv = SymmetryOp::inversion();
191
192    // Rotation generators
193    let c2z = SymmetryOp {
194        rotation: [[-1, 0, 0], [0, -1, 0], [0, 0, 1]],
195        translation: [0.0, 0.0, 0.0],
196    };
197    let c2y = SymmetryOp {
198        rotation: [[-1, 0, 0], [0, 1, 0], [0, 0, -1]],
199        translation: [0.0, 0.0, 0.0],
200    };
201    let c2x = SymmetryOp {
202        rotation: [[1, 0, 0], [0, -1, 0], [0, 0, -1]],
203        translation: [0.0, 0.0, 0.0],
204    };
205    let c4z = SymmetryOp {
206        rotation: [[0, -1, 0], [1, 0, 0], [0, 0, 1]],
207        translation: [0.0, 0.0, 0.0],
208    };
209    let c4z_inv = SymmetryOp {
210        rotation: [[0, 1, 0], [-1, 0, 0], [0, 0, 1]],
211        translation: [0.0, 0.0, 0.0],
212    };
213    let c3z = SymmetryOp {
214        rotation: [[0, -1, 0], [1, -1, 0], [0, 0, 1]],
215        translation: [0.0, 0.0, 0.0],
216    };
217    let c3z_inv = SymmetryOp {
218        rotation: [[-1, 1, 0], [-1, 0, 0], [0, 0, 1]],
219        translation: [0.0, 0.0, 0.0],
220    };
221    let c6z = SymmetryOp {
222        rotation: [[1, -1, 0], [1, 0, 0], [0, 0, 1]],
223        translation: [0.0, 0.0, 0.0],
224    };
225    let c6z_inv = SymmetryOp {
226        rotation: [[0, 1, 0], [-1, 1, 0], [0, 0, 1]],
227        translation: [0.0, 0.0, 0.0],
228    };
229
230    // Mirror planes
231    let mz = SymmetryOp {
232        rotation: [[1, 0, 0], [0, 1, 0], [0, 0, -1]],
233        translation: [0.0, 0.0, 0.0],
234    };
235    let my = SymmetryOp {
236        rotation: [[1, 0, 0], [0, -1, 0], [0, 0, 1]],
237        translation: [0.0, 0.0, 0.0],
238    };
239    let mx = SymmetryOp {
240        rotation: [[-1, 0, 0], [0, 1, 0], [0, 0, 1]],
241        translation: [0.0, 0.0, 0.0],
242    };
243
244    // Screw axes and glide planes
245    let s21z = SymmetryOp {
246        rotation: [[-1, 0, 0], [0, -1, 0], [0, 0, 1]],
247        translation: [0.0, 0.0, 0.5],
248    };
249    let s21y = SymmetryOp {
250        rotation: [[-1, 0, 0], [0, 1, 0], [0, 0, -1]],
251        translation: [0.0, 0.5, 0.0],
252    };
253    let s21x = SymmetryOp {
254        rotation: [[1, 0, 0], [0, -1, 0], [0, 0, -1]],
255        translation: [0.5, 0.0, 0.0],
256    };
257
258    // Cubic diagonal rotations
259    let c3_111 = SymmetryOp {
260        rotation: [[0, 0, 1], [1, 0, 0], [0, 1, 0]],
261        translation: [0.0, 0.0, 0.0],
262    };
263    let c3_111_inv = SymmetryOp {
264        rotation: [[0, 1, 0], [0, 0, 1], [1, 0, 0]],
265        translation: [0.0, 0.0, 0.0],
266    };
267
268    let result: (&str, &str, LatticeType, Vec<SymmetryOp>) = match number {
269        // ─── Triclinic ───
270        1 => ("P 1", "1", P, vec![id]),
271        2 => ("P -1", "-1", P, vec![id, inv]),
272
273        // ─── Monoclinic ───
274        3 => ("P 2", "2", P, vec![id, c2y.clone()]),
275        4 => ("P 21", "2", P, vec![id, s21y.clone()]),
276        5 => ("C 2", "2", C, vec![id, c2y.clone()]),
277        6 => ("P m", "m", P, vec![id, my.clone()]),
278        7 => (
279            "P c",
280            "m",
281            P,
282            vec![
283                id,
284                SymmetryOp {
285                    rotation: [[1, 0, 0], [0, -1, 0], [0, 0, 1]],
286                    translation: [0.0, 0.0, 0.5],
287                },
288            ],
289        ),
290        8 => ("C m", "m", C, vec![id, my.clone()]),
291        9 => (
292            "C c",
293            "m",
294            C,
295            vec![
296                id,
297                SymmetryOp {
298                    rotation: [[1, 0, 0], [0, -1, 0], [0, 0, 1]],
299                    translation: [0.0, 0.0, 0.5],
300                },
301            ],
302        ),
303        10 => (
304            "P 2/m",
305            "2/m",
306            P,
307            vec![id, c2y.clone(), inv.clone(), my.clone()],
308        ),
309        11 => (
310            "P 21/m",
311            "2/m",
312            P,
313            vec![
314                id,
315                s21y.clone(),
316                inv.clone(),
317                SymmetryOp {
318                    rotation: [[1, 0, 0], [0, -1, 0], [0, 0, 1]],
319                    translation: [0.0, 0.5, 0.0],
320                },
321            ],
322        ),
323        12 => (
324            "C 2/m",
325            "2/m",
326            C,
327            vec![id, c2y.clone(), inv.clone(), my.clone()],
328        ),
329        13 => (
330            "P 2/c",
331            "2/m",
332            P,
333            vec![
334                id,
335                c2y.clone(),
336                inv.clone(),
337                SymmetryOp {
338                    rotation: [[1, 0, 0], [0, -1, 0], [0, 0, 1]],
339                    translation: [0.0, 0.0, 0.5],
340                },
341            ],
342        ),
343        14 => (
344            "P 21/c",
345            "2/m",
346            P,
347            vec![
348                id,
349                s21y.clone(),
350                inv.clone(),
351                SymmetryOp {
352                    rotation: [[1, 0, 0], [0, -1, 0], [0, 0, 1]],
353                    translation: [0.0, 0.5, 0.5],
354                },
355            ],
356        ),
357        15 => (
358            "C 2/c",
359            "2/m",
360            C,
361            vec![
362                id,
363                c2y.clone(),
364                inv.clone(),
365                SymmetryOp {
366                    rotation: [[1, 0, 0], [0, -1, 0], [0, 0, 1]],
367                    translation: [0.0, 0.0, 0.5],
368                },
369            ],
370        ),
371
372        // ─── Orthorhombic ───
373        16 => (
374            "P 2 2 2",
375            "222",
376            P,
377            vec![id, c2z.clone(), c2y.clone(), c2x.clone()],
378        ),
379        17 => (
380            "P 2 2 21",
381            "222",
382            P,
383            vec![id, c2z.clone(), c2y.clone(), s21x.clone()],
384        ),
385        18 => (
386            "P 21 21 2",
387            "222",
388            P,
389            vec![id, c2z.clone(), s21y.clone(), s21x.clone()],
390        ),
391        19 => (
392            "P 21 21 21",
393            "222",
394            P,
395            vec![id, s21z.clone(), s21y.clone(), s21x.clone()],
396        ),
397        20 => (
398            "C 2 2 21",
399            "222",
400            C,
401            vec![id, c2z.clone(), c2y.clone(), s21x.clone()],
402        ),
403        21 => (
404            "C 2 2 2",
405            "222",
406            C,
407            vec![id, c2z.clone(), c2y.clone(), c2x.clone()],
408        ),
409        22 => (
410            "F 2 2 2",
411            "222",
412            F,
413            vec![id, c2z.clone(), c2y.clone(), c2x.clone()],
414        ),
415        23 => (
416            "I 2 2 2",
417            "222",
418            I,
419            vec![id, c2z.clone(), c2y.clone(), c2x.clone()],
420        ),
421        24 => (
422            "I 21 21 21",
423            "222",
424            I,
425            vec![id, s21z.clone(), s21y.clone(), s21x.clone()],
426        ),
427        25 => (
428            "P m m 2",
429            "mm2",
430            P,
431            vec![id, c2z.clone(), mx.clone(), my.clone()],
432        ),
433        26..=46 => {
434            let (sym, lt) = match number {
435                26 => ("P m c 21", P),
436                27 => ("P c c 2", P),
437                28 => ("P m a 2", P),
438                29 => ("P c a 21", P),
439                30 => ("P n c 2", P),
440                31 => ("P m n 21", P),
441                32 => ("P b a 2", P),
442                33 => ("P n a 21", P),
443                34 => ("P n n 2", P),
444                35 => ("C m m 2", C),
445                36 => ("C m c 21", C),
446                37 => ("C c c 2", C),
447                38 => ("A m m 2", A),
448                39 => ("A b m 2", A),
449                40 => ("A m a 2", A),
450                41 => ("A b a 2", A),
451                42 => ("F m m 2", F),
452                43 => ("F d d 2", F),
453                44 => ("I m m 2", I),
454                45 => ("I b a 2", I),
455                46 => ("I m a 2", I),
456                _ => unreachable!(),
457            };
458            // Point group mm2: identity, C2z, mirror_x, mirror_y
459            (
460                sym,
461                "mm2",
462                lt,
463                vec![id, c2z.clone(), mx.clone(), my.clone()],
464            )
465        }
466        47..=74 => {
467            let ops = vec![
468                id,
469                c2z.clone(),
470                c2y.clone(),
471                c2x.clone(),
472                inv.clone(),
473                mz.clone(),
474                my.clone(),
475                mx.clone(),
476            ];
477            let lt = match number {
478                65..=68 => C,
479                69 => F,
480                70 => F,
481                71..=74 => I,
482                _ => P,
483            };
484            let sym = orthorhombic_symbol(number);
485            (sym, "mmm", lt, ops)
486        }
487
488        // ─── Tetragonal ───
489        75 => (
490            "P 4",
491            "4",
492            P,
493            vec![id, c4z.clone(), c2z.clone(), c4z_inv.clone()],
494        ),
495        76..=80 => {
496            let sym = tetragonal_symbol(number);
497            let lt = if (79..=80).contains(&number) { I } else { P };
498            (
499                sym,
500                "4",
501                lt,
502                vec![id, c4z.clone(), c2z.clone(), c4z_inv.clone()],
503            )
504        }
505        81 => (
506            "P -4",
507            "-4",
508            P,
509            vec![id, c4z.clone(), c2z.clone(), c4z_inv.clone()],
510        ),
511        82 => (
512            "I -4",
513            "-4",
514            I,
515            vec![id, c4z.clone(), c2z.clone(), c4z_inv.clone()],
516        ),
517        83..=88 => {
518            let sym = tetragonal_symbol(number);
519            let lt = if number == 87 || number == 88 { I } else { P };
520            let ops = vec![
521                id,
522                c4z.clone(),
523                c2z.clone(),
524                c4z_inv.clone(),
525                inv.clone(),
526                mz.clone(),
527                my.clone(),
528                mx.clone(),
529            ];
530            (sym, "4/m", lt, ops)
531        }
532        89..=98 => {
533            let sym = tetragonal_symbol(number);
534            let lt = if number == 97 || number == 98 { I } else { P };
535            let ops = vec![
536                id,
537                c4z.clone(),
538                c2z.clone(),
539                c4z_inv.clone(),
540                c2x.clone(),
541                c2y.clone(),
542            ];
543            (sym, "422", lt, ops)
544        }
545        99..=110 => {
546            let sym = tetragonal_symbol(number);
547            let lt = if number == 107 || number == 108 || number == 109 || number == 110 {
548                I
549            } else {
550                P
551            };
552            let ops = vec![
553                id,
554                c4z.clone(),
555                c2z.clone(),
556                c4z_inv.clone(),
557                mx.clone(),
558                my.clone(),
559            ];
560            (sym, "4mm", lt, ops)
561        }
562        111..=122 => {
563            let sym = tetragonal_symbol(number);
564            let lt = if number >= 119 { I } else { P };
565            let ops = vec![
566                id,
567                c4z.clone(),
568                c2z.clone(),
569                c4z_inv.clone(),
570                mx.clone(),
571                my.clone(),
572            ];
573            (sym, "-42m", lt, ops)
574        }
575        123..=142 => {
576            let sym = tetragonal_symbol(number);
577            let lt = if number >= 139 { I } else { P };
578            let ops = vec![
579                id,
580                c4z.clone(),
581                c2z.clone(),
582                c4z_inv.clone(),
583                c2x.clone(),
584                c2y.clone(),
585                inv.clone(),
586                mz.clone(),
587                my.clone(),
588                mx.clone(),
589            ];
590            (sym, "4/mmm", lt, ops)
591        }
592
593        // ─── Trigonal ───
594        143..=146 => {
595            let sym = trigonal_symbol(number);
596            let lt = if number == 146 { R } else { P };
597            (sym, "3", lt, vec![id, c3z.clone(), c3z_inv.clone()])
598        }
599        147..=148 => {
600            let sym = trigonal_symbol(number);
601            let lt = if number == 148 { R } else { P };
602            let ops = vec![id, c3z.clone(), c3z_inv.clone(), inv.clone()];
603            (sym, "-3", lt, ops)
604        }
605        149..=155 => {
606            let sym = trigonal_symbol(number);
607            let lt = if number == 155 { R } else { P };
608            let ops = vec![id, c3z.clone(), c3z_inv.clone(), c2x.clone()];
609            (sym, "32", lt, ops)
610        }
611        156..=161 => {
612            let sym = trigonal_symbol(number);
613            let lt = if number == 160 || number == 161 { R } else { P };
614            let ops = vec![id, c3z.clone(), c3z_inv.clone(), my.clone()];
615            (sym, "3m", lt, ops)
616        }
617        162..=167 => {
618            let sym = trigonal_symbol(number);
619            let lt = if number == 166 || number == 167 { R } else { P };
620            let ops = vec![
621                id,
622                c3z.clone(),
623                c3z_inv.clone(),
624                c2x.clone(),
625                inv.clone(),
626                my.clone(),
627            ];
628            (sym, "-3m", lt, ops)
629        }
630
631        // ─── Hexagonal ───
632        168..=173 => {
633            let sym = hexagonal_symbol(number);
634            let ops = vec![
635                id,
636                c6z.clone(),
637                c3z.clone(),
638                c2z.clone(),
639                c3z_inv.clone(),
640                c6z_inv.clone(),
641            ];
642            (sym, "6", P, ops)
643        }
644        174 => (
645            "P -6",
646            "-6",
647            P,
648            vec![id, c3z.clone(), c3z_inv.clone(), mz.clone()],
649        ),
650        175..=176 => {
651            let sym = hexagonal_symbol(number);
652            let ops = vec![
653                id,
654                c6z.clone(),
655                c3z.clone(),
656                c2z.clone(),
657                c3z_inv.clone(),
658                c6z_inv.clone(),
659                inv.clone(),
660                mz.clone(),
661            ];
662            (sym, "6/m", P, ops)
663        }
664        177..=182 => {
665            let sym = hexagonal_symbol(number);
666            let ops = vec![
667                id,
668                c6z.clone(),
669                c3z.clone(),
670                c2z.clone(),
671                c3z_inv.clone(),
672                c6z_inv.clone(),
673                c2x.clone(),
674                c2y.clone(),
675            ];
676            (sym, "622", P, ops)
677        }
678        183..=186 => {
679            let sym = hexagonal_symbol(number);
680            let ops = vec![
681                id,
682                c6z.clone(),
683                c3z.clone(),
684                c2z.clone(),
685                c3z_inv.clone(),
686                c6z_inv.clone(),
687                mx.clone(),
688                my.clone(),
689            ];
690            (sym, "6mm", P, ops)
691        }
692        187..=190 => {
693            let sym = hexagonal_symbol(number);
694            let ops = vec![
695                id,
696                c3z.clone(),
697                c3z_inv.clone(),
698                mz.clone(),
699                mx.clone(),
700                my.clone(),
701            ];
702            (sym, "-6m2", P, ops)
703        }
704        191..=194 => {
705            let sym = hexagonal_symbol(number);
706            let ops = vec![
707                id,
708                c6z.clone(),
709                c3z.clone(),
710                c2z.clone(),
711                c3z_inv.clone(),
712                c6z_inv.clone(),
713                c2x.clone(),
714                c2y.clone(),
715                inv.clone(),
716                mz.clone(),
717                mx.clone(),
718                my.clone(),
719            ];
720            (sym, "6/mmm", P, ops)
721        }
722
723        // ─── Cubic ───
724        195..=199 => {
725            let sym = cubic_symbol(number);
726            let lt = match number {
727                196 => F,
728                197 | 199 => I,
729                _ => P,
730            };
731            let ops = vec![
732                id,
733                c2z.clone(),
734                c2y.clone(),
735                c2x.clone(),
736                c3_111.clone(),
737                c3_111_inv.clone(),
738            ];
739            (sym, "23", lt, ops)
740        }
741        200..=206 => {
742            let sym = cubic_symbol(number);
743            let lt = match number {
744                202 | 203 => F,
745                204 | 206 => I,
746                _ => P,
747            };
748            let ops = vec![
749                id,
750                c2z.clone(),
751                c2y.clone(),
752                c2x.clone(),
753                c3_111.clone(),
754                c3_111_inv.clone(),
755                inv.clone(),
756            ];
757            (sym, "m-3", lt, ops)
758        }
759        207..=214 => {
760            let sym = cubic_symbol(number);
761            let lt = match number {
762                209 | 210 => F,
763                211 | 214 => I,
764                _ => P,
765            };
766            let ops = vec![
767                id,
768                c4z.clone(),
769                c4z_inv.clone(),
770                c2z.clone(),
771                c2y.clone(),
772                c2x.clone(),
773                c3_111.clone(),
774                c3_111_inv.clone(),
775            ];
776            (sym, "432", lt, ops)
777        }
778        215..=220 => {
779            let sym = cubic_symbol(number);
780            let lt = match number {
781                216 | 219 => F,
782                217 | 220 => I,
783                _ => P,
784            };
785            let ops = vec![
786                id,
787                c2z.clone(),
788                c2y.clone(),
789                c2x.clone(),
790                c3_111.clone(),
791                c3_111_inv.clone(),
792                mx.clone(),
793                my.clone(),
794                mz.clone(),
795            ];
796            (sym, "-43m", lt, ops)
797        }
798        221..=230 => {
799            let sym = cubic_symbol(number);
800            let lt = match number {
801                225..=228 => F,
802                229 | 230 => I,
803                _ => P,
804            };
805            let ops = vec![
806                id,
807                c4z.clone(),
808                c4z_inv.clone(),
809                c2z.clone(),
810                c2y.clone(),
811                c2x.clone(),
812                c3_111.clone(),
813                c3_111_inv.clone(),
814                inv.clone(),
815                mx.clone(),
816                my.clone(),
817                mz.clone(),
818            ];
819            (sym, "m-3m", lt, ops)
820        }
821
822        _ => return None,
823    };
824    Some(result)
825}
826
827fn orthorhombic_symbol(n: u16) -> &'static str {
828    match n {
829        47 => "P m m m",
830        48 => "P n n n",
831        49 => "P c c m",
832        50 => "P b a n",
833        51 => "P m m a",
834        52 => "P n n a",
835        53 => "P m n a",
836        54 => "P c c a",
837        55 => "P b a m",
838        56 => "P c c n",
839        57 => "P b c m",
840        58 => "P n n m",
841        59 => "P m m n",
842        60 => "P b c n",
843        61 => "P b c a",
844        62 => "P n m a",
845        63 => "C m c m",
846        64 => "C m c a",
847        65 => "C m m m",
848        66 => "C c c m",
849        67 => "C m m a",
850        68 => "C c c a",
851        69 => "F m m m",
852        70 => "F d d d",
853        71 => "I m m m",
854        72 => "I b a m",
855        73 => "I b c a",
856        74 => "I m m a",
857        _ => "?",
858    }
859}
860
861fn tetragonal_symbol(n: u16) -> &'static str {
862    match n {
863        75 => "P 4",
864        76 => "P 41",
865        77 => "P 42",
866        78 => "P 43",
867        79 => "I 4",
868        80 => "I 41",
869        81 => "P -4",
870        82 => "I -4",
871        83 => "P 4/m",
872        84 => "P 42/m",
873        85 => "P 4/n",
874        86 => "P 42/n",
875        87 => "I 4/m",
876        88 => "I 41/a",
877        89 => "P 4 2 2",
878        90 => "P 4 21 2",
879        91 => "P 41 2 2",
880        92 => "P 41 21 2",
881        93 => "P 42 2 2",
882        94 => "P 42 21 2",
883        95 => "P 43 2 2",
884        96 => "P 43 21 2",
885        97 => "I 4 2 2",
886        98 => "I 41 2 2",
887        99 => "P 4 m m",
888        100 => "P 4 b m",
889        101 => "P 42 c m",
890        102 => "P 42 n m",
891        103 => "P 4 c c",
892        104 => "P 4 n c",
893        105 => "P 42 m c",
894        106 => "P 42 b c",
895        107 => "I 4 m m",
896        108 => "I 4 c m",
897        109 => "I 41 m d",
898        110 => "I 41 c d",
899        111 => "P -4 2 m",
900        112 => "P -4 2 c",
901        113 => "P -4 21 m",
902        114 => "P -4 21 c",
903        115 => "P -4 m 2",
904        116 => "P -4 c 2",
905        117 => "P -4 b 2",
906        118 => "P -4 n 2",
907        119 => "I -4 m 2",
908        120 => "I -4 c 2",
909        121 => "I -4 2 m",
910        122 => "I -4 2 d",
911        123 => "P 4/m m m",
912        124 => "P 4/m c c",
913        125 => "P 4/n b m",
914        126 => "P 4/n n c",
915        127 => "P 4/m b m",
916        128 => "P 4/m n c",
917        129 => "P 4/n m m",
918        130 => "P 4/n c c",
919        131 => "P 42/m m c",
920        132 => "P 42/m c m",
921        133 => "P 42/n b c",
922        134 => "P 42/n n m",
923        135 => "P 42/m b c",
924        136 => "P 42/m n m",
925        137 => "P 42/n m c",
926        138 => "P 42/n c m",
927        139 => "I 4/m m m",
928        140 => "I 4/m c m",
929        141 => "I 41/a m d",
930        142 => "I 41/a c d",
931        _ => "?",
932    }
933}
934
935fn trigonal_symbol(n: u16) -> &'static str {
936    match n {
937        143 => "P 3",
938        144 => "P 31",
939        145 => "P 32",
940        146 => "R 3",
941        147 => "P -3",
942        148 => "R -3",
943        149 => "P 3 1 2",
944        150 => "P 3 2 1",
945        151 => "P 31 1 2",
946        152 => "P 31 2 1",
947        153 => "P 32 1 2",
948        154 => "P 32 2 1",
949        155 => "R 3 2",
950        156 => "P 3 m 1",
951        157 => "P 3 1 m",
952        158 => "P 3 c 1",
953        159 => "P 3 1 c",
954        160 => "R 3 m",
955        161 => "R 3 c",
956        162 => "P -3 1 m",
957        163 => "P -3 1 c",
958        164 => "P -3 m 1",
959        165 => "P -3 c 1",
960        166 => "R -3 m",
961        167 => "R -3 c",
962        _ => "?",
963    }
964}
965
966fn hexagonal_symbol(n: u16) -> &'static str {
967    match n {
968        168 => "P 6",
969        169 => "P 61",
970        170 => "P 65",
971        171 => "P 62",
972        172 => "P 64",
973        173 => "P 63",
974        174 => "P -6",
975        175 => "P 6/m",
976        176 => "P 63/m",
977        177 => "P 6 2 2",
978        178 => "P 61 2 2",
979        179 => "P 65 2 2",
980        180 => "P 62 2 2",
981        181 => "P 64 2 2",
982        182 => "P 63 2 2",
983        183 => "P 6 m m",
984        184 => "P 6 c c",
985        185 => "P 63 c m",
986        186 => "P 63 m c",
987        187 => "P -6 m 2",
988        188 => "P -6 c 2",
989        189 => "P -6 2 m",
990        190 => "P -6 2 c",
991        191 => "P 6/m m m",
992        192 => "P 6/m c c",
993        193 => "P 63/m c m",
994        194 => "P 63/m m c",
995        _ => "?",
996    }
997}
998
999fn cubic_symbol(n: u16) -> &'static str {
1000    match n {
1001        195 => "P 2 3",
1002        196 => "F 2 3",
1003        197 => "I 2 3",
1004        198 => "P 21 3",
1005        199 => "I 21 3",
1006        200 => "P m -3",
1007        201 => "P n -3",
1008        202 => "F m -3",
1009        203 => "F d -3",
1010        204 => "I m -3",
1011        205 => "P a -3",
1012        206 => "I a -3",
1013        207 => "P 4 3 2",
1014        208 => "P 42 3 2",
1015        209 => "F 4 3 2",
1016        210 => "F 41 3 2",
1017        211 => "I 4 3 2",
1018        212 => "P 43 3 2",
1019        213 => "P 41 3 2",
1020        214 => "I 41 3 2",
1021        215 => "P -4 3 m",
1022        216 => "F -4 3 m",
1023        217 => "I -4 3 m",
1024        218 => "P -4 3 n",
1025        219 => "F -4 3 c",
1026        220 => "I -4 3 d",
1027        221 => "P m -3 m",
1028        222 => "P n -3 n",
1029        223 => "P m -3 n",
1030        224 => "P n -3 m",
1031        225 => "F m -3 m",
1032        226 => "F m -3 c",
1033        227 => "F d -3 m",
1034        228 => "F d -3 c",
1035        229 => "I m -3 m",
1036        230 => "I a -3 d",
1037        _ => "?",
1038    }
1039}
1040
1041/// Get all 230 space group numbers and symbols.
1042pub fn all_space_groups() -> Vec<(u16, &'static str)> {
1043    let mut result = Vec::with_capacity(230);
1044    for n in 1..=230u16 {
1045        let sym = match crystal_system_for_number(n) {
1046            Some(CrystalSystem::Triclinic) => match n {
1047                1 => "P 1",
1048                2 => "P -1",
1049                _ => "?",
1050            },
1051            Some(CrystalSystem::Monoclinic) => match n {
1052                3 => "P 2",
1053                4 => "P 21",
1054                5 => "C 2",
1055                6 => "P m",
1056                7 => "P c",
1057                8 => "C m",
1058                9 => "C c",
1059                10 => "P 2/m",
1060                11 => "P 21/m",
1061                12 => "C 2/m",
1062                13 => "P 2/c",
1063                14 => "P 21/c",
1064                15 => "C 2/c",
1065                _ => "?",
1066            },
1067            Some(CrystalSystem::Orthorhombic) => {
1068                if n >= 47 {
1069                    orthorhombic_symbol(n)
1070                } else {
1071                    match n {
1072                        16 => "P 2 2 2",
1073                        17 => "P 2 2 21",
1074                        18 => "P 21 21 2",
1075                        19 => "P 21 21 21",
1076                        20 => "C 2 2 21",
1077                        21 => "C 2 2 2",
1078                        22 => "F 2 2 2",
1079                        23 => "I 2 2 2",
1080                        24 => "I 21 21 21",
1081                        25 => "P m m 2",
1082                        26 => "P m c 21",
1083                        27 => "P c c 2",
1084                        28 => "P m a 2",
1085                        29 => "P c a 21",
1086                        30 => "P n c 2",
1087                        31 => "P m n 21",
1088                        32 => "P b a 2",
1089                        33 => "P n a 21",
1090                        34 => "P n n 2",
1091                        35 => "C m m 2",
1092                        36 => "C m c 21",
1093                        37 => "C c c 2",
1094                        38 => "A m m 2",
1095                        39 => "A b m 2",
1096                        40 => "A m a 2",
1097                        41 => "A b a 2",
1098                        42 => "F m m 2",
1099                        43 => "F d d 2",
1100                        44 => "I m m 2",
1101                        45 => "I b a 2",
1102                        46 => "I m a 2",
1103                        _ => "?",
1104                    }
1105                }
1106            }
1107            Some(CrystalSystem::Tetragonal) => tetragonal_symbol(n),
1108            Some(CrystalSystem::Trigonal) => trigonal_symbol(n),
1109            Some(CrystalSystem::Hexagonal) => hexagonal_symbol(n),
1110            Some(CrystalSystem::Cubic) => cubic_symbol(n),
1111            None => "?",
1112        };
1113        result.push((n, sym));
1114    }
1115    result
1116}
1117
1118#[cfg(test)]
1119mod tests {
1120    use super::*;
1121
1122    #[test]
1123    fn test_all_230_groups_exist() {
1124        for n in 1..=230u16 {
1125            let sg = space_group_by_number(n);
1126            assert!(sg.is_some(), "Missing space group #{}", n);
1127        }
1128    }
1129
1130    #[test]
1131    fn test_crystal_systems() {
1132        assert_eq!(
1133            space_group_by_number(1).unwrap().crystal_system,
1134            CrystalSystem::Triclinic
1135        );
1136        assert_eq!(
1137            space_group_by_number(14).unwrap().crystal_system,
1138            CrystalSystem::Monoclinic
1139        );
1140        assert_eq!(
1141            space_group_by_number(62).unwrap().crystal_system,
1142            CrystalSystem::Orthorhombic
1143        );
1144        assert_eq!(
1145            space_group_by_number(225).unwrap().crystal_system,
1146            CrystalSystem::Cubic
1147        );
1148    }
1149
1150    #[test]
1151    fn test_p1_identity_only() {
1152        let sg = space_group_by_number(1).unwrap();
1153        assert_eq!(sg.operations.len(), 1);
1154        let pos = sg.generate_equivalent_positions(&[0.25, 0.3, 0.4]);
1155        assert_eq!(pos.len(), 1);
1156    }
1157
1158    #[test]
1159    fn test_p_minus_1() {
1160        let sg = space_group_by_number(2).unwrap();
1161        assert_eq!(sg.operations.len(), 2);
1162        let pos = sg.generate_equivalent_positions(&[0.1, 0.2, 0.3]);
1163        assert_eq!(pos.len(), 2); // (x,y,z) and (-x,-y,-z)
1164    }
1165
1166    #[test]
1167    fn test_fm3m_225() {
1168        let sg = space_group_by_number(225).unwrap();
1169        assert_eq!(sg.symbol, "F m -3 m");
1170        assert_eq!(sg.crystal_system, CrystalSystem::Cubic);
1171        assert_eq!(sg.lattice_type, LatticeType::F);
1172    }
1173
1174    #[test]
1175    fn test_lookup_by_symbol() {
1176        let sg = space_group_by_symbol("P 21/c").unwrap();
1177        assert_eq!(sg.number, 14);
1178    }
1179
1180    #[test]
1181    fn test_all_symbols_list() {
1182        let groups = all_space_groups();
1183        assert_eq!(groups.len(), 230);
1184    }
1185}