1use serde::{Deserialize, Serialize};
12
13#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27pub enum LatticeType {
28 P,
30 I,
32 F,
34 C,
36 R,
38 A,
40 B,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct SymmetryOp {
47 pub rotation: [[i8; 3]; 3],
49 pub translation: [f64; 3],
51}
52
53impl SymmetryOp {
54 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 result[i] = result[i] - result[i].floor();
64 }
65 result
66 }
67
68 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct SpaceGroup {
88 pub number: u16,
90 pub symbol: String,
92 pub crystal_system: CrystalSystem,
94 pub lattice_type: LatticeType,
96 pub point_group: String,
98 pub operations: Vec<SymmetryOp>,
100}
101
102impl SpaceGroup {
103 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 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 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 pub fn multiplicity(&self) -> usize {
128 self.operations.len()
129 }
130}
131
132pub fn space_group_by_number(number: u16) -> Option<SpaceGroup> {
134 build_space_group(number)
135}
136
137pub 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
150pub 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
164fn 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
181fn 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 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 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 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 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 1 => ("P 1", "1", P, vec![id]),
271 2 => ("P -1", "-1", P, vec![id, inv]),
272
273 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 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 (
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 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 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 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 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
1041pub 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); }
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}