qsym2/drivers/representation_analysis/
angular_function.rs1use anyhow::{self, ensure, format_err};
4use derive_builder::Builder;
5use nalgebra::{Point3, Vector3};
6use ndarray::{Array1, Array2};
7use num_complex::{Complex, ComplexFloat};
8use rayon::prelude::*;
9
10use crate::analysis::{EigenvalueComparisonMode, RepAnalysis};
11use crate::angmom::sh_conversion::sh_cart2rl_mat;
12use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled};
13use crate::auxiliary::atom::{Atom, ElementMap};
14use crate::auxiliary::molecule::Molecule;
15use crate::basis::ao::{
16 BasisAngularOrder, BasisAtom, BasisShell, CartOrder, PureOrder, ShellOrder, SpinorOrder,
17 SpinorParticleType, cart_tuple_to_str,
18};
19use crate::chartab::SubspaceDecomposable;
20use crate::chartab::chartab_group::CharacterProperties;
21use crate::io::format::{log_subtitle, qsym2_output};
22use crate::symmetry::symmetry_group::SymmetryGroupProperties;
23use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
24use crate::target::determinant::SlaterDeterminant;
25use crate::target::orbital::orbital_analysis::generate_det_mo_orbits;
26use crate::target::tensor::axialvector::axialvector_analysis::AxialVector3SymmetryOrbit;
27use crate::target::tensor::axialvector::{AxialVector3, TimeParity};
28
29#[derive(Clone, Builder, Debug)]
39pub struct AngularFunctionRepAnalysisParams {
40 #[builder(default = "1e-7")]
42 pub integrality_threshold: f64,
43
44 #[builder(default = "1e-7")]
46 pub linear_independence_threshold: f64,
47
48 #[builder(default = "2")]
50 pub max_angular_momentum: u32,
51}
52
53impl AngularFunctionRepAnalysisParams {
54 pub fn builder() -> AngularFunctionRepAnalysisParamsBuilder {
56 AngularFunctionRepAnalysisParamsBuilder::default()
57 }
58}
59
60impl Default for AngularFunctionRepAnalysisParams {
61 fn default() -> Self {
62 Self::builder()
63 .build()
64 .expect("Unable to build as default `AngularFunctionRepAnalysisParams`.")
65 }
66}
67
68pub(crate) fn find_angular_function_representation<G>(
81 group: &G,
82 params: &AngularFunctionRepAnalysisParams,
83) -> Result<(), anyhow::Error>
84where
85 G: SymmetryGroupProperties + Clone + Send + Sync,
86 G::CharTab: SubspaceDecomposable<f64>,
87 <<G as CharacterProperties>::CharTab as SubspaceDecomposable<f64>>::Decomposition: Send + Sync,
88{
89 let emap = ElementMap::new();
90 let thresh = group
91 .elements()
92 .clone()
93 .into_iter()
94 .next()
95 .ok_or_else(|| format_err!("No symmetry operation found."))?
96 .generating_element
97 .threshold();
98 let mol = Molecule::from_atoms(
99 &[Atom::new_ordinary("H", Point3::origin(), &emap, thresh)],
100 thresh,
101 );
102 let lmax = params.max_angular_momentum;
103
104 let (pure_symss, cart_symss) = (0..=lmax).fold(
105 (
106 Vec::with_capacity(usize::try_from(lmax)?),
107 Vec::with_capacity(usize::try_from(lmax)?),
108 ),
109 |mut acc, l| {
110 [
111 ShellOrder::Pure(PureOrder::increasingm(l)),
112 ShellOrder::Cart(CartOrder::lex(l)),
113 ]
114 .iter()
115 .for_each(|shell_order| {
116 let bao = BasisAngularOrder::new(&[BasisAtom::new(
117 &mol.atoms[0],
118 &[BasisShell::new(l, shell_order.clone())],
119 )]);
120 let nbas = bao.n_funcs();
121 let cs = vec![Array2::<f64>::eye(nbas)];
122 let occs = vec![Array1::<f64>::ones(nbas)];
123 let sao = match shell_order {
124 ShellOrder::Pure(_) | ShellOrder::Spinor(_) => {
125 Array2::<f64>::eye(bao.n_funcs())
126 }
127 ShellOrder::Cart(cartorder) => {
128 let cart2rl =
129 sh_cart2rl_mat(l, l, cartorder, true, &PureOrder::increasingm(l));
130 cart2rl.mapv(ComplexFloat::conj).t().dot(&cart2rl)
131 }
132 };
133
134 let mo_symmetries = SlaterDeterminant::<f64, SpinConstraint>::builder()
135 .structure_constraint(SpinConstraint::Restricted(1))
136 .baos(vec![&bao])
137 .complex_symmetric(false)
138 .mol(&mol)
139 .coefficients(&cs)
140 .occupations(&occs)
141 .threshold(params.linear_independence_threshold)
142 .build()
143 .map_err(|err| format_err!(err))
144 .and_then(|det| {
145 let mos = det.to_orbitals();
146 generate_det_mo_orbits(
147 &det,
148 &mos,
149 group,
150 &sao,
151 None, params.integrality_threshold,
153 params.linear_independence_threshold,
154 SymmetryTransformationKind::Spatial,
155 EigenvalueComparisonMode::Real,
156 true,
157 )
158 .map(|(_, mut mo_orbitss)| {
159 mo_orbitss[0]
160 .par_iter_mut()
161 .map(|mo_orbit| {
162 mo_orbit.calc_xmat(false)?;
163 mo_orbit.analyse_rep().map_err(|err| format_err!(err))
164 })
165 .collect::<Vec<_>>()
166 })
167 });
168 match shell_order {
169 ShellOrder::Pure(_) => acc.0.push(mo_symmetries),
170 ShellOrder::Cart(_) => acc.1.push(mo_symmetries),
171 ShellOrder::Spinor(_) => {
172 panic!("Unexpected spinor shell in `find_angular_function_representation`.")
173 }
174 }
175 });
176 acc
177 },
178 );
179
180 let pure_sym_strss = pure_symss
181 .into_iter()
182 .map(|l_pure_syms_opt| {
183 l_pure_syms_opt
184 .map(|l_pure_syms| {
185 l_pure_syms
186 .into_iter()
187 .map(|sym_opt| {
188 sym_opt
189 .map(|sym| sym.to_string())
190 .unwrap_or_else(|err| err.to_string())
191 })
192 .collect::<Vec<_>>()
193 })
194 .unwrap_or_else(|err| vec![err.to_string()])
195 })
196 .collect::<Vec<_>>();
197 let cart_sym_strss = cart_symss
198 .into_iter()
199 .map(|l_cart_syms_opt| {
200 l_cart_syms_opt
201 .map(|l_cart_syms| {
202 l_cart_syms
203 .into_iter()
204 .map(|sym_opt| {
205 sym_opt
206 .map(|sym| sym.to_string())
207 .unwrap_or_else(|err| err.to_string())
208 })
209 .collect::<Vec<_>>()
210 })
211 .unwrap_or_else(|err| vec![err.to_string()])
212 })
213 .collect::<Vec<_>>();
214 let rot_sym_strs = [Vector3::x(), Vector3::y(), Vector3::z()]
215 .into_iter()
216 .map(|v| {
217 let rv = AxialVector3::<f64>::builder()
218 .components(v)
219 .time_parity(TimeParity::Even)
220 .threshold(params.linear_independence_threshold)
221 .build()
222 .map_err(|err| format_err!(err))?;
223 let mut orbit_rv = AxialVector3SymmetryOrbit::builder()
224 .group(group)
225 .origin(&rv)
226 .integrality_threshold(params.integrality_threshold)
227 .linear_independence_threshold(params.linear_independence_threshold)
228 .symmetry_transformation_kind(SymmetryTransformationKind::Spatial)
229 .eigenvalue_comparison_mode(EigenvalueComparisonMode::Real)
230 .build()
231 .map_err(|err| format_err!(err))?;
232 let _ = orbit_rv
233 .calc_smat(None, None, true)
234 .unwrap()
235 .calc_xmat(false);
236 orbit_rv
237 .analyse_rep()
238 .map(|sym| sym.to_string())
239 .map_err(|err| format_err!(err))
240 })
241 .map(|sym| sym.unwrap_or_else(|err| err.to_string()))
242 .collect::<Vec<_>>();
243
244 let pure_sym_width = pure_sym_strss
245 .iter()
246 .flat_map(|syms| syms.iter().map(|sym| sym.chars().count()))
247 .max()
248 .unwrap_or(9)
249 .max(9);
250 let cart_sym_width = cart_sym_strss
251 .iter()
252 .flat_map(|syms| syms.iter().map(|sym| sym.chars().count()))
253 .max()
254 .unwrap_or(9)
255 .max(9);
256 let rot_sym_width = rot_sym_strs
257 .iter()
258 .map(|sym| sym.chars().count())
259 .max()
260 .unwrap_or(8)
261 .max(8);
262
263 let l_width = lmax.to_string().chars().count();
264 let pure_width = (l_width + 1).max(4);
265 let cart_width = usize::try_from(lmax)?.max(4);
266 let rot_width = 3;
267
268 log_subtitle(&format!(
269 "Space-fixed spatial angular function symmetries in {}",
270 group.finite_subgroup_name().unwrap_or(&group.name())
271 ));
272 qsym2_output!("");
273 if lmax < 1 {
274 qsym2_output!(
275 "{}",
276 "┈".repeat(l_width + pure_width + pure_sym_width + cart_width + cart_sym_width + 11)
277 );
278 qsym2_output!(
279 " {:>l_width$} {:>pure_width$} {:<pure_sym_width$} {:>cart_width$} {:<}",
280 "l",
281 "Pure",
282 "Pure sym.",
283 "Cart",
284 "Cart sym."
285 );
286 qsym2_output!(
287 "{}",
288 "┈".repeat(l_width + pure_width + pure_sym_width + cart_width + cart_sym_width + 11)
289 );
290 } else {
291 qsym2_output!(
292 "{}",
293 "┈".repeat(
294 l_width
295 + pure_width
296 + pure_sym_width
297 + cart_width
298 + cart_sym_width
299 + rot_width
300 + rot_sym_width
301 + 15
302 )
303 );
304 qsym2_output!(
305 " {:>l_width$} {:>pure_width$} {:<pure_sym_width$} {:>cart_width$} {:<cart_sym_width$} {:>rot_width$} {:<}",
306 "l",
307 "Pure",
308 "Pure sym.",
309 "Cart",
310 "Cart sym.",
311 "Rot",
312 "Rot sym."
313 );
314 qsym2_output!(
315 "{}",
316 "┈".repeat(
317 l_width
318 + pure_width
319 + pure_sym_width
320 + cart_width
321 + cart_sym_width
322 + rot_width
323 + rot_sym_width
324 + 15
325 )
326 );
327 }
328
329 let empty_str = String::new();
330 (0..=usize::try_from(lmax)?).for_each(|l| {
331 if l > 0 {
332 qsym2_output!("");
333 }
334 let n_pure = 2 * l + 1;
335 let mut i_pure = 0;
336 let mut i_rot = 0;
337
338 let l_u32 = u32::try_from(l).unwrap_or_else(|err| panic!("{err}"));
339 let cartorder = CartOrder::lex(l_u32);
340 cartorder
341 .iter()
342 .enumerate()
343 .for_each(|(i_cart, cart_tuple)| {
344 let l_str = if i_cart == 0 {
345 format!("{l:>l_width$}")
346 } else {
347 " ".repeat(l_width)
348 };
349
350 let pure_str = if i_pure < n_pure {
351 let pure_str = format!(
352 "{:>pure_width$} {:<pure_sym_width$}",
353 if i_pure < l {
354 format!("-{}", i_pure.abs_diff(l))
355 } else {
356 format!("+{}", i_pure.abs_diff(l))
357 },
358 pure_sym_strss
359 .get(l)
360 .and_then(|l_pure_sym_strs| l_pure_sym_strs.get(i_pure))
361 .unwrap_or(&empty_str)
362 );
363 i_pure += 1;
364 pure_str
365 } else {
366 " ".repeat(pure_width + pure_sym_width + 2)
367 };
368
369 let cart_symbol = cart_tuple_to_str(cart_tuple, true);
370 let cart_str = if l == 1 {
371 format!(
373 "{cart_symbol:>cart_width$} {:<cart_sym_width$}",
374 cart_sym_strss
375 .get(l)
376 .and_then(|l_cart_sym_strs| l_cart_sym_strs.get(i_cart))
377 .unwrap_or(&empty_str)
378 )
379 } else {
380 format!(
382 "{cart_symbol:>cart_width$} {:<}",
383 cart_sym_strss
384 .get(l)
385 .and_then(|l_cart_sym_strs| l_cart_sym_strs.get(i_cart))
386 .unwrap_or(&empty_str)
387 )
388 };
389
390 if l == 1 && i_rot < 3 {
391 let rot_str = format!(
392 "{:>rot_width$} {:<}",
393 match i_rot {
394 0 => "Rx",
395 1 => "Ry",
396 2 => "Rz",
397 _ => "--",
398 },
399 rot_sym_strs.get(i_rot).unwrap_or(&"--".to_string())
400 );
401 i_rot += 1;
402 qsym2_output!(" {l_str} {pure_str} {cart_str} {rot_str}");
403 } else {
404 qsym2_output!(" {l_str} {pure_str} {cart_str}");
405 }
406 });
407 });
408 if lmax < 1 {
409 qsym2_output!(
410 "{}",
411 "┈".repeat(l_width + pure_width + pure_sym_width + cart_width + cart_sym_width + 11)
412 );
413 } else {
414 qsym2_output!(
415 "{}",
416 "┈".repeat(
417 l_width
418 + pure_width
419 + pure_sym_width
420 + cart_width
421 + cart_sym_width
422 + rot_width
423 + rot_sym_width
424 + 15
425 )
426 );
427 }
428 qsym2_output!("");
429
430 Ok(())
431}
432
433pub(crate) fn find_spinor_function_representation<G>(
441 group: &G,
442 params: &AngularFunctionRepAnalysisParams,
443) -> Result<(), anyhow::Error>
444where
445 G: SymmetryGroupProperties + Clone + Send + Sync,
446 G::CharTab: SubspaceDecomposable<Complex<f64>>,
447 <<G as CharacterProperties>::CharTab as SubspaceDecomposable<Complex<f64>>>::Decomposition:
448 Send + Sync,
449{
450 ensure!(
451 group.is_double_group(),
452 "The specified group is not a double group."
453 );
454
455 let emap = ElementMap::new();
456 let thresh = group
457 .elements()
458 .clone()
459 .into_iter()
460 .next()
461 .ok_or_else(|| format_err!("No symmetry operation found."))?
462 .generating_element
463 .threshold();
464 let mol = Molecule::from_atoms(
465 &[Atom::new_ordinary("H", Point3::origin(), &emap, thresh)],
466 thresh,
467 );
468 let lmax = params.max_angular_momentum;
469
470 let spinor_symss = (1..2 * lmax).step_by(2).fold(
471 Vec::with_capacity(2 * usize::try_from(lmax)?),
472 |mut acc, two_j| {
473 let even_first = two_j.rem_euclid(4) == 1;
474 let shell_order_g = ShellOrder::Spinor(SpinorOrder::increasingm(
475 two_j,
476 even_first,
477 SpinorParticleType::Fermion(None),
478 ));
479 let shell_order_u = ShellOrder::Spinor(SpinorOrder::increasingm(
480 two_j,
481 !even_first,
482 SpinorParticleType::Fermion(None),
483 ));
484 let bao_g = BasisAngularOrder::new(&[BasisAtom::new(
485 &mol.atoms[0],
486 &[BasisShell::new(two_j, shell_order_g)],
487 )]);
488 let bao_u = BasisAngularOrder::new(&[BasisAtom::new(
489 &mol.atoms[0],
490 &[BasisShell::new(two_j, shell_order_u)],
491 )]);
492 let nbas = bao_g.n_funcs();
493 let cs = vec![Array2::<Complex<f64>>::eye(nbas)];
494 let occs = vec![Array1::<f64>::ones(nbas)];
495 let sao = Array2::<Complex<f64>>::eye(bao_g.n_funcs());
496
497 for bao in [bao_g, bao_u] {
498 let mo_symmetries = SlaterDeterminant::<Complex<f64>, SpinOrbitCoupled>::builder()
499 .structure_constraint(SpinOrbitCoupled::JAdapted(1))
500 .baos(vec![&bao])
501 .complex_symmetric(false)
502 .mol(&mol)
503 .coefficients(&cs)
504 .occupations(&occs)
505 .threshold(params.linear_independence_threshold)
506 .build()
507 .map_err(|err| format_err!(err))
508 .and_then(|det| {
509 let mos = det.to_orbitals();
510 generate_det_mo_orbits(
511 &det,
512 &mos,
513 group,
514 &sao,
515 None, params.integrality_threshold,
517 params.linear_independence_threshold,
518 SymmetryTransformationKind::SpinSpatial,
519 EigenvalueComparisonMode::Modulus,
520 true,
521 )
522 .map(|(_, mut mo_orbitss)| {
523 mo_orbitss[0]
524 .par_iter_mut()
525 .map(|mo_orbit| {
526 mo_orbit.calc_xmat(false)?;
527 mo_orbit.analyse_rep().map_err(|err| format_err!(err))
528 })
529 .collect::<Vec<_>>()
530 })
531 });
532 acc.push(mo_symmetries);
533 }
534 acc
535 },
536 );
537
538 let spinor_sym_strss = spinor_symss
539 .into_iter()
540 .map(|l_spinor_syms_opt| {
541 l_spinor_syms_opt
542 .map(|l_spinor_syms| {
543 l_spinor_syms
544 .into_iter()
545 .map(|sym_opt| {
546 sym_opt
547 .map(|sym| sym.to_string())
548 .unwrap_or_else(|err| err.to_string())
549 })
550 .collect::<Vec<_>>()
551 })
552 .unwrap_or_else(|err| vec![err.to_string()])
553 })
554 .collect::<Vec<_>>();
555
556 let spinor_sym_width = spinor_sym_strss
557 .iter()
558 .flat_map(|syms| syms.iter().map(|sym| sym.chars().count()))
559 .max()
560 .unwrap_or(11)
561 .max(11);
562
563 let j_width = format!("{}/2", 2 * lmax - 1).chars().count();
564 let l_width = format!("(+; l = {lmax})").chars().count();
565 let jl_width = j_width + l_width + 1;
566 let mj_width = (j_width + 1).max(6);
567
568 log_subtitle(&format!(
569 "Space-fixed spinor function symmetries in {}",
570 group.finite_subgroup_name().unwrap_or(&group.name())
571 ));
572 qsym2_output!("");
573 qsym2_output!("{}", "┈".repeat(jl_width + mj_width + spinor_sym_width + 6));
574 qsym2_output!(
575 " {:>jl_width$} {:>mj_width$} {:<}",
576 "j",
577 "Spinor",
578 "Spinor sym.",
579 );
580 qsym2_output!("{}", "┈".repeat(jl_width + mj_width + spinor_sym_width + 6));
581
582 let empty_str = String::new();
583 (1..usize::try_from(2 * lmax)?)
584 .step_by(2)
585 .enumerate()
586 .for_each(|(i_two_j, two_j)| {
587 if two_j > 1 {
588 qsym2_output!("");
589 }
590 let n_spinor = two_j + 1;
591
592 let even_first = two_j.rem_euclid(4) == 1;
593 let two_j_u32 = u32::try_from(two_j).unwrap_or_else(|err| panic!("{err}"));
594 let spinororder_g =
595 SpinorOrder::increasingm(two_j_u32, even_first, SpinorParticleType::Fermion(None));
596 spinororder_g
597 .iter()
598 .enumerate()
599 .for_each(|(i_spinor, two_mj)| {
600 let j_str = if i_spinor == 0 {
601 let j_str_temp = format!(
602 "{two_j}/2 ({}; l = {})",
603 if spinororder_g.a() == 1 { "+" } else { "-" },
604 spinororder_g.l()
605 );
606 format!("{j_str_temp:>jl_width$}")
607 } else {
608 " ".repeat(jl_width)
609 };
610
611 let spinor_str = if i_spinor < n_spinor {
612 let spinor_str_temp = format!("{two_mj:+}/2");
613 let spinor_str = format!(
614 "{spinor_str_temp:>mj_width$} {:<}",
615 spinor_sym_strss
616 .get(i_two_j * 2)
617 .and_then(|l_spinor_sym_strs| l_spinor_sym_strs.get(i_spinor))
618 .unwrap_or(&empty_str)
619 );
620 spinor_str
621 } else {
622 " ".repeat(mj_width + spinor_sym_width + 2)
623 };
624
625 qsym2_output!(" {j_str} {spinor_str}");
626 });
627
628 qsym2_output!("");
629
630 let spinororder_u =
631 SpinorOrder::increasingm(two_j_u32, !even_first, SpinorParticleType::Fermion(None));
632 spinororder_u
633 .iter()
634 .enumerate()
635 .for_each(|(i_spinor, two_mj)| {
636 let j_str = if i_spinor == 0 {
637 let j_str_temp = format!(
638 "{two_j}/2 ({}; l = {})",
639 if spinororder_u.a() == 1 { "+" } else { "-" },
640 spinororder_u.l()
641 );
642 format!("{j_str_temp:>jl_width$}")
643 } else {
644 " ".repeat(jl_width)
645 };
646
647 let spinor_str = if i_spinor < n_spinor {
648 let spinor_str_temp = format!("{two_mj:+}/2");
649 let spinor_str = format!(
650 "{spinor_str_temp:>mj_width$} {:<}",
651 spinor_sym_strss
652 .get(i_two_j * 2 + 1)
653 .and_then(|l_spinor_sym_strs| l_spinor_sym_strs.get(i_spinor))
654 .unwrap_or(&empty_str)
655 );
656 spinor_str
657 } else {
658 " ".repeat(mj_width + spinor_sym_width + 2)
659 };
660
661 qsym2_output!(" {j_str} {spinor_str}");
662 });
663 });
664 qsym2_output!("{}", "┈".repeat(jl_width + mj_width + spinor_sym_width + 6));
665 qsym2_output!("");
666
667 Ok(())
668}