crystallographic_group/hall_symbols/
mod.rs1use 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 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 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 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}