crystallographic_group/hall_symbols/
mod.rs

1use std::{
2    collections::{HashMap, HashSet},
3    fmt::Display,
4};
5
6use nalgebra::{Matrix3, Vector3};
7use winnow::ModalResult;
8
9use crate::{
10    database::{SpaceGroupHallSymbol, ORDER_12, ORDER_24, ORDER_48},
11    utils::positive_mod_stbn_i32,
12};
13
14use self::{
15    lattice_symbol::LatticeSymbol,
16    matrix_symbol::{MatrixSymbol, NFold, NFoldDiag},
17    origin_shift::OriginShift,
18    parser::parse_hall_symbol,
19};
20
21mod general_positions;
22mod lattice_symbol;
23mod matrix_symbol;
24mod origin_shift;
25mod parser;
26mod translation_symbol;
27
28pub use general_positions::GeneralPositions;
29pub use matrix_symbol::SeitzMatrix;
30
31pub(crate) const SEITZ_TRANSLATE_BASE_NUMBER: i32 = 12;
32
33pub trait SymmetryElement {
34    fn equiv_num(&self) -> usize;
35}
36
37#[derive(Debug, Clone, PartialEq, PartialOrd)]
38pub struct HallSymbolNotation {
39    lattice_symbol: LatticeSymbol,
40    matrix_symbols: Vec<MatrixSymbol>,
41    origin_shift: OriginShift,
42}
43
44impl HallSymbolNotation {
45    pub fn new(
46        lattice_symbol: LatticeSymbol,
47        matrix_symbols: Vec<MatrixSymbol>,
48        origin_shift: OriginShift,
49    ) -> Self {
50        Self {
51            lattice_symbol,
52            matrix_symbols,
53            origin_shift,
54        }
55    }
56    pub fn try_from_str(input: &str) -> ModalResult<Self> {
57        let mut input = input;
58        parse_hall_symbol(&mut input)
59    }
60
61    fn num_generators(&self) -> usize {
62        self.lattice_symbol.equiv_num() + self.matrix_symbols.len()
63    }
64
65    fn max_equiv_pos(&self) -> usize {
66        self.matrix_symbols
67            .iter()
68            .map(|m| m.seitz_matrix().expect("Invalid Seitz Matrix").equiv_num())
69            .fold(self.lattice_symbol.equiv_num(), |acc, x| acc * x)
70    }
71
72    fn get_matrice_order(&self) -> Vec<&str> {
73        let first_m = self.matrix_symbols.first().unwrap();
74        match first_m.nfold_body() {
75            NFold::N6 => ORDER_24.to_vec(),
76            NFold::N3 => match first_m.nfold_diag() {
77                NFoldDiag::Asterisk => ORDER_12.to_vec(),
78                _ => ORDER_24.to_vec(),
79            },
80            _ => ORDER_48.to_vec(),
81        }
82    }
83
84    fn sort_general_positions(&self, positions: &[SeitzMatrix]) -> Vec<SeitzMatrix> {
85        let mut ret_position: Vec<SeitzMatrix> = positions.to_vec();
86        let order_to_use = self.get_matrice_order();
87        ret_position.sort_by(|a, b| {
88            let a_id = order_to_use
89                .iter()
90                .position(|&s| s == a.jones_faithful_repr_rot())
91                .unwrap_or_else(|| panic!("{} fails to match", a.jones_faithful_repr_rot()));
92            let b_id = order_to_use
93                .iter()
94                .position(|&s| s == b.jones_faithful_repr_rot())
95                .unwrap_or_else(|| panic!("{} fails to match", b.jones_faithful_repr_rot()));
96            a_id.cmp(&b_id)
97        });
98        ret_position.to_vec()
99    }
100
101    /// Find the minimal positive x,y,z for the translation vector,
102    /// by considering the lattice translation vectors.
103    fn translation_minimal_repr(&self, new_translation: Vector3<i32>) -> Vector3<i32> {
104        let new_translation_pos = new_translation.map(positive_mod_stbn_i32);
105        self.lattice_symbol
106            .get_translations()
107            .iter()
108            .map(|tr| {
109                let v = new_translation + tr;
110                v.map(positive_mod_stbn_i32)
111            })
112            .fold(new_translation_pos, |curr, next| {
113                if curr.map(|v| v as f64).norm_squared() > next.map(|v| v as f64).norm_squared()
114                    && curr.iter().filter(|&&v| v <= 0).count()
115                        >= next.iter().filter(|&&v| v <= 0).count()
116                {
117                    next
118                } else {
119                    curr
120                }
121            })
122    }
123
124    /// Add a `SeitzMatrix` to a `Vec<SeitzMatrix>` if it is unique.
125    /// Returns true when the matrix is unique and adding is successful, other wise false.
126    fn add_to_list(
127        &self,
128        list: &mut Vec<SeitzMatrix>,
129        map: &mut HashMap<Matrix3<i32>, HashSet<Vector3<i32>>>,
130        mut new_matrix: SeitzMatrix,
131    ) -> bool {
132        match map.get_mut(&new_matrix.rotation_part()) {
133            None => {
134                let mut translation_set = HashSet::new();
135                let tr_with_min_pos_component =
136                    self.translation_minimal_repr(new_matrix.translation_part());
137                translation_set.insert(tr_with_min_pos_component);
138                map.insert(new_matrix.rotation_part(), translation_set);
139                new_matrix.set_translation_part(tr_with_min_pos_component);
140                list.push(new_matrix);
141                true
142            }
143            Some(tr_set) => {
144                if self.lattice_symbol.get_translations().iter().all(|tr| {
145                    let t = (new_matrix.translation_part() + tr).map(positive_mod_stbn_i32);
146                    tr_set.get(&t).is_none()
147                        && (new_matrix.translation_part() + tr)
148                            .map(|v| v % SEITZ_TRANSLATE_BASE_NUMBER)
149                            .iter()
150                            .all(|&v| v != 0)
151                }) {
152                    let tr_with_min_pos_component =
153                        self.translation_minimal_repr(new_matrix.translation_part());
154                    if tr_set.insert(tr_with_min_pos_component) {
155                        new_matrix.set_translation_part(tr_with_min_pos_component);
156                        list.push(new_matrix);
157                        true
158                    } else {
159                        false
160                    }
161                } else {
162                    false
163                }
164            }
165        }
166    }
167
168    fn generate_positions(&self) -> Vec<SeitzMatrix> {
169        // let num_generators = self.num_generators();
170        let mut list: Vec<SeitzMatrix> = Vec::with_capacity(self.max_equiv_pos());
171        let mut matrice_map: HashMap<Matrix3<i32>, HashSet<Vector3<i32>>> = HashMap::new();
172        self.lattice_symbol.seitz_matrices().iter().for_each(|&m| {
173            self.add_to_list(&mut list, &mut matrice_map, m);
174        });
175        self.matrix_symbols.iter().for_each(|ms| {
176            let seitz_mx = ms
177                .seitz_matrix()
178                .unwrap_or_else(|_| panic!("SeitzMatrix generation failed for {}", ms));
179            let shifted = self.origin_shift.shifted_matrix(seitz_mx);
180            self.add_to_list(&mut list, &mut matrice_map, shifted);
181        });
182        loop {
183            let mut list_cloned = list.clone();
184            for i in list.iter().skip(1) {
185                for j in list.iter().skip(1) {
186                    let new_m = *i * *j;
187                    if self.add_to_list(&mut list_cloned, &mut matrice_map, new_m) {
188                        break;
189                    }
190                }
191            }
192            if list_cloned.len() > list.len() {
193                list = list_cloned;
194            } else {
195                break;
196            }
197        }
198        self.sort_general_positions(&list)
199    }
200    pub fn general_positions(&self) -> GeneralPositions {
201        GeneralPositions::new(
202            self.lattice_symbol.get_translations(),
203            self.generate_positions(),
204        )
205    }
206}
207
208impl From<SpaceGroupHallSymbol> for HallSymbolNotation {
209    fn from(value: SpaceGroupHallSymbol) -> Self {
210        Self::try_from_str(&value.get_hall_symbol()).unwrap()
211    }
212}
213
214impl Display for HallSymbolNotation {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        let lattice_symbol = format!("{}", self.lattice_symbol);
217        let matrice = self
218            .matrix_symbols
219            .iter()
220            .map(|m| format!("{m}"))
221            .collect::<Vec<String>>()
222            .join(" ");
223        let origin_shift = if self.origin_shift != OriginShift::default() {
224            format!(" {}", self.origin_shift)
225        } else {
226            String::new()
227        };
228        write!(f, "{} {}{}", lattice_symbol, matrice, origin_shift)
229    }
230}
231
232#[cfg(test)]
233mod test {
234
235    use std::{collections::HashSet, fs::read_to_string, path::Path};
236
237    use indicatif::ProgressIterator;
238
239    use crate::database::DEFAULT_SPACE_GROUP_SYMBOLS;
240
241    use super::{
242        matrix_symbol::{MatrixSymbol, NFold, NFoldSub},
243        translation_symbol::TranslationSymbol,
244        HallSymbolNotation,
245    };
246
247    #[test]
248    fn test_p178() {
249        let symbol_str = "P 61 2 (0 0 -1)";
250        let p178 = HallSymbolNotation::try_from_str(symbol_str);
251        let general_positions = p178.unwrap().general_positions();
252        println!(
253            "Number of positions: {}",
254            general_positions.num_of_general_pos()
255        );
256    }
257    #[test]
258    fn test_p5() {
259        let symbol_str: &str = "C 2y";
260        let p_5 = HallSymbolNotation::try_from_str(symbol_str).unwrap();
261        let general_positions = p_5.general_positions();
262        println!(
263            "Number of positions: {}",
264            general_positions.num_of_general_pos()
265        );
266        println!("{general_positions}");
267    }
268    #[test]
269    fn test_150() {
270        let symbol_str = "P 3 2\"";
271        let p_150 = HallSymbolNotation::try_from_str(symbol_str).unwrap();
272        dbg!(p_150.matrix_symbols);
273    }
274    #[test]
275    fn test_228() {
276        test("-F 4ud 2vw 3");
277    }
278    #[test]
279    fn test_221() {
280        test("-P 4 2 3")
281    }
282    #[test]
283    fn test_229() {
284        test("-I 4 2 3")
285    }
286    #[test]
287    fn test_91() {
288        test("P 4w 2c")
289    }
290    #[test]
291    fn test_45() {
292        test("I 2 -2c")
293    }
294    #[test]
295    fn test_80() {
296        let c1 = MatrixSymbol::new_builder()
297            .set_nfold_body(NFold::N4)
298            .set_nfold_sub(NFoldSub::N1)
299            .set_translation_symbols(Some(vec![TranslationSymbol::B]))
300            .build()
301            .unwrap();
302        let c2 = MatrixSymbol::new_builder()
303            .set_nfold_body(NFold::N4)
304            .set_translation_symbols(Some(vec![TranslationSymbol::B, TranslationSymbol::W]))
305            .build()
306            .unwrap();
307        println!(
308            "{}, {}",
309            c1.seitz_matrix().unwrap(),
310            c2.seitz_matrix().unwrap()
311        );
312        println!("{}", c1.seitz_matrix().unwrap().powi(2));
313        println!("{}", c2.seitz_matrix().unwrap().powi(2));
314        test("-I 41b")
315    }
316
317    #[test]
318    fn test_all() {
319        let default_list = DEFAULT_SPACE_GROUP_SYMBOLS.get(2).unwrap();
320        default_list
321            .iter()
322            .progress()
323            .map(|&symbol| {
324                HashSet::<String>::from_iter(
325                    HallSymbolNotation::try_from_str(symbol)
326                        .unwrap()
327                        .general_positions()
328                        .pure_txt(),
329                )
330            })
331            .enumerate()
332            .for_each(|(i, xyz_repr)| {
333                let ref_path = Path::new(env!("CARGO_MANIFEST_DIR"))
334                    .join("refs")
335                    .join(format!("{}.txt", i + 1));
336                let ref_content = read_to_string(ref_path)
337                    .unwrap()
338                    .lines()
339                    .map(|s| s.to_string())
340                    .collect::<HashSet<String>>();
341                if ref_content != xyz_repr {
342                    println!("{}: {}", i + 1, default_list[i]);
343                    println!("ref:\n{:?}", ref_content);
344                    println!("this:\n{:?}", xyz_repr);
345                }
346            })
347    }
348
349    fn test(symbol_str: &str) {
350        let g = HallSymbolNotation::try_from_str(symbol_str).unwrap();
351        let positions = g.general_positions();
352        println!("Number of positions: {}", positions.num_of_general_pos());
353        println!("{}", positions.text_format());
354    }
355
356    fn xyz_repr(symbol_str: &str) -> String {
357        let g = HallSymbolNotation::try_from_str(symbol_str).unwrap();
358        let positions = g.general_positions();
359        positions.text_format()
360    }
361
362    fn read_from_refs() -> Vec<Vec<String>> {
363        todo!()
364    }
365}