1use std::fmt;
4use std::ops::Mul;
5
6use anyhow::{self, bail, format_err};
7use derive_builder::Builder;
8use duplicate::duplicate_item;
9use nalgebra::Point3;
10use ndarray::Array1;
11use ndarray_linalg::types::Lapack;
12use num_complex::{Complex, ComplexFloat};
13use num_traits::Float;
14use serde::{Deserialize, Serialize};
15
16use crate::analysis::{
17 EigenvalueComparisonMode, Orbit, Overlap, ProjectionDecomposition, RepAnalysis,
18 log_overlap_eigenvalues,
19};
20use crate::chartab::SubspaceDecomposable;
21use crate::chartab::chartab_group::CharacterProperties;
22use crate::drivers::QSym2Driver;
23use crate::drivers::representation_analysis::angular_function::{
24 AngularFunctionRepAnalysisParams, find_angular_function_representation,
25 find_spinor_function_representation,
26};
27use crate::drivers::representation_analysis::{
28 CharacterTableDisplay, MagneticSymmetryAnalysisKind, fn_construct_magnetic_group,
29 fn_construct_unitary_group, log_cc_transversal,
30};
31use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
32use crate::group::{GroupProperties, MagneticRepresentedGroup, UnitaryRepresentedGroup};
33use crate::io::format::{
34 QSym2Output, log_subtitle, nice_bool, qsym2_output, write_subtitle, write_title,
35};
36use crate::sandbox::target::real_space_function::RealSpaceFunction;
37use crate::sandbox::target::real_space_function::real_space_function_analysis::RealSpaceFunctionSymmetryOrbit;
38use crate::symmetry::symmetry_group::{
39 MagneticRepresentedSymmetryGroup, SymmetryGroupProperties, UnitaryRepresentedSymmetryGroup,
40};
41use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
42
43#[cfg(test)]
44#[path = "real_space_function_tests.rs"]
45mod real_space_function_tests;
46
47const fn default_true() -> bool {
56 true
57}
58const fn default_symbolic() -> Option<CharacterTableDisplay> {
59 Some(CharacterTableDisplay::Symbolic)
60}
61
62#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
64pub struct RealSpaceFunctionRepAnalysisParams<T: From<f64>> {
65 pub integrality_threshold: T,
67
68 pub linear_independence_threshold: T,
70
71 #[builder(default = "None")]
74 #[serde(default)]
75 pub use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
76
77 #[builder(default = "false")]
79 #[serde(default)]
80 pub use_double_group: bool,
81
82 #[builder(default = "true")]
85 #[serde(default = "default_true")]
86 pub use_cayley_table: bool,
87
88 #[builder(default = "SymmetryTransformationKind::Spatial")]
91 #[serde(default)]
92 pub symmetry_transformation_kind: SymmetryTransformationKind,
93
94 #[builder(default = "Some(CharacterTableDisplay::Symbolic)")]
97 #[serde(default = "default_symbolic")]
98 pub write_character_table: Option<CharacterTableDisplay>,
99
100 #[builder(default = "true")]
102 #[serde(default = "default_true")]
103 pub write_overlap_eigenvalues: bool,
104
105 #[builder(default = "EigenvalueComparisonMode::Modulus")]
107 #[serde(default)]
108 pub eigenvalue_comparison_mode: EigenvalueComparisonMode,
109
110 #[builder(default = "None")]
113 #[serde(default)]
114 pub infinite_order_to_finite: Option<u32>,
115}
116
117impl<T> RealSpaceFunctionRepAnalysisParams<T>
118where
119 T: Float + From<f64>,
120{
121 pub fn builder() -> RealSpaceFunctionRepAnalysisParamsBuilder<T> {
123 RealSpaceFunctionRepAnalysisParamsBuilder::default()
124 }
125}
126
127impl Default for RealSpaceFunctionRepAnalysisParams<f64> {
128 fn default() -> Self {
129 Self::builder()
130 .integrality_threshold(1e-7)
131 .linear_independence_threshold(1e-7)
132 .build()
133 .expect("Unable to construct a default `RealSpaceFunctionRepAnalysisParams<f64>`.")
134 }
135}
136
137impl<T> fmt::Display for RealSpaceFunctionRepAnalysisParams<T>
138where
139 T: From<f64> + fmt::LowerExp + fmt::Debug,
140{
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 writeln!(
143 f,
144 "Integrality threshold: {:.3e}",
145 self.integrality_threshold
146 )?;
147 writeln!(
148 f,
149 "Linear independence threshold: {:.3e}",
150 self.linear_independence_threshold
151 )?;
152 writeln!(
153 f,
154 "Orbit eigenvalue comparison mode: {}",
155 self.eigenvalue_comparison_mode
156 )?;
157 writeln!(
158 f,
159 "Write overlap eigenvalues: {}",
160 nice_bool(self.write_overlap_eigenvalues)
161 )?;
162 writeln!(f)?;
163 writeln!(
164 f,
165 "Use magnetic group for analysis: {}",
166 match self.use_magnetic_group {
167 None => "no",
168 Some(MagneticSymmetryAnalysisKind::Representation) =>
169 "yes, using unitary representations",
170 Some(MagneticSymmetryAnalysisKind::Corepresentation) =>
171 "yes, using magnetic corepresentations",
172 }
173 )?;
174 writeln!(
175 f,
176 "Use double group for analysis: {}",
177 nice_bool(self.use_double_group)
178 )?;
179 writeln!(
180 f,
181 "Use Cayley table for orbit overlap matrices: {}",
182 nice_bool(self.use_cayley_table)
183 )?;
184 if let Some(finite_order) = self.infinite_order_to_finite {
185 writeln!(f, "Infinite order to finite: {finite_order}")?;
186 }
187 writeln!(
188 f,
189 "Symmetry transformation kind: {}",
190 self.symmetry_transformation_kind
191 )?;
192 writeln!(f)?;
193 writeln!(
194 f,
195 "Write character table: {}",
196 if let Some(chartab_display) = self.write_character_table.as_ref() {
197 format!("yes, {}", chartab_display.to_string().to_lowercase())
198 } else {
199 "no".to_string()
200 }
201 )?;
202
203 Ok(())
204 }
205}
206
207#[derive(Clone, Builder)]
213pub struct RealSpaceFunctionRepAnalysisResult<'a, G, T, F>
214where
215 G: SymmetryGroupProperties + Clone,
216 G::CharTab: SubspaceDecomposable<T>,
217 T: ComplexFloat + Lapack,
218 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
219 F: Clone + Fn(&Point3<f64>) -> T,
220{
221 parameters: &'a RealSpaceFunctionRepAnalysisParams<<T as ComplexFloat>::Real>,
224
225 real_space_function: &'a RealSpaceFunction<T, F>,
227
228 group: G,
230
231 real_space_function_symmetry:
233 Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>,
234}
235
236impl<'a, G, T, F> RealSpaceFunctionRepAnalysisResult<'a, G, T, F>
237where
238 G: SymmetryGroupProperties + Clone,
239 G::CharTab: SubspaceDecomposable<T>,
240 T: ComplexFloat + Lapack,
241 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
242 F: Clone + Fn(&Point3<f64>) -> T,
243{
244 pub fn builder() -> RealSpaceFunctionRepAnalysisResultBuilder<'a, G, T, F> {
246 RealSpaceFunctionRepAnalysisResultBuilder::default()
247 }
248}
249
250impl<'a, G, T, F> fmt::Display for RealSpaceFunctionRepAnalysisResult<'a, G, T, F>
251where
252 G: SymmetryGroupProperties + Clone,
253 G::CharTab: SubspaceDecomposable<T>,
254 T: ComplexFloat + Lapack,
255 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
256 F: Clone + Fn(&Point3<f64>) -> T,
257{
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 write_subtitle(f, "Orbit-based symmetry analysis results")?;
260 writeln!(f)?;
261 writeln!(
262 f,
263 "> Group: {} ({})",
264 self.group
265 .finite_subgroup_name()
266 .map(|subgroup_name| format!("{} > {}", self.group.name(), subgroup_name))
267 .unwrap_or(self.group.name()),
268 self.group.group_type().to_string().to_lowercase()
269 )?;
270 writeln!(f)?;
271 writeln!(f, "> Overall real-space function result")?;
272 writeln!(
273 f,
274 " Grid size: {} {}",
275 self.real_space_function.grid_points().len(),
276 if self.real_space_function.grid_points().len() == 1 {
277 "point"
278 } else {
279 "points"
280 }
281 )?;
282 writeln!(
283 f,
284 " Symmetry: {}",
285 self.real_space_function_symmetry
286 .as_ref()
287 .map(|s| s.to_string())
288 .unwrap_or_else(|err| format!("-- ({err})"))
289 )?;
290 writeln!(f)?;
291
292 Ok(())
293 }
294}
295
296impl<'a, G, T, F> fmt::Debug for RealSpaceFunctionRepAnalysisResult<'a, G, T, F>
297where
298 G: SymmetryGroupProperties + Clone,
299 G::CharTab: SubspaceDecomposable<T>,
300 T: ComplexFloat + Lapack,
301 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
302 F: Clone + Fn(&Point3<f64>) -> T,
303{
304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305 writeln!(f, "{self}")
306 }
307}
308
309impl<'a, G, T, F> RealSpaceFunctionRepAnalysisResult<'a, G, T, F>
310where
311 G: SymmetryGroupProperties + Clone,
312 G::CharTab: SubspaceDecomposable<T>,
313 T: ComplexFloat + Lapack,
314 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
315 F: Clone + Fn(&Point3<f64>) -> T,
316{
317 pub fn real_space_function_symmetry(
319 &self,
320 ) -> &Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String> {
321 &self.real_space_function_symmetry
322 }
323
324 pub fn parameters(&self) -> &RealSpaceFunctionRepAnalysisParams<<T as ComplexFloat>::Real> {
326 self.parameters
327 }
328}
329
330#[derive(Clone, Builder)]
340#[builder(build_fn(validate = "Self::validate"))]
341pub struct RealSpaceFunctionRepAnalysisDriver<'a, G, T, F>
342where
343 G: SymmetryGroupProperties + Clone,
344 G::CharTab: SubspaceDecomposable<T>,
345 T: ComplexFloat + Lapack,
346 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
347 F: Clone + Fn(&Point3<f64>) -> T,
348{
349 parameters: &'a RealSpaceFunctionRepAnalysisParams<<T as ComplexFloat>::Real>,
351
352 real_space_function: &'a RealSpaceFunction<T, F>,
354
355 symmetry_group: &'a SymmetryGroupDetectionResult,
358
359 weight: &'a Array1<T>,
361
362 angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
364
365 #[builder(setter(skip), default = "None")]
367 result: Option<RealSpaceFunctionRepAnalysisResult<'a, G, T, F>>,
368}
369
370impl<'a, G, T, F> RealSpaceFunctionRepAnalysisDriverBuilder<'a, G, T, F>
371where
372 G: SymmetryGroupProperties + Clone,
373 G::CharTab: SubspaceDecomposable<T>,
374 T: ComplexFloat + Lapack,
375 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
376 F: Clone + Fn(&Point3<f64>) -> T,
377{
378 fn validate(&self) -> Result<(), String> {
379 let _ = self
380 .real_space_function
381 .ok_or("No real-space function specified.".to_string())?;
382
383 let params = self.parameters.ok_or(
384 "No real-space function representation analysis parameters found.".to_string(),
385 )?;
386
387 let sym_res = self
388 .symmetry_group
389 .ok_or("No symmetry group information found.".to_string())?;
390
391 let sym = if params.use_magnetic_group.is_some() {
392 sym_res
393 .magnetic_symmetry
394 .as_ref()
395 .ok_or("Magnetic symmetry requested for representation analysis, but no magnetic symmetry found.")?
396 } else {
397 &sym_res.unitary_symmetry
398 };
399
400 if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
401 Err(format!(
402 "Representation analysis cannot be performed using the entirety of the infinite group `{}`. \
403 Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
404 sym.group_name
405 .as_ref()
406 .expect("No symmetry group name found.")
407 ))
408 } else {
409 Ok(())
410 }
411 }
412}
413
414impl<'a, G, T, F> RealSpaceFunctionRepAnalysisDriver<'a, G, T, F>
422where
423 G: SymmetryGroupProperties + Clone,
424 G::CharTab: SubspaceDecomposable<T>,
425 T: ComplexFloat + Lapack,
426 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
427 F: Clone + Fn(&Point3<f64>) -> T,
428{
429 pub fn builder() -> RealSpaceFunctionRepAnalysisDriverBuilder<'a, G, T, F> {
431 RealSpaceFunctionRepAnalysisDriverBuilder::default()
432 }
433}
434
435impl<'a, T, F> RealSpaceFunctionRepAnalysisDriver<'a, UnitaryRepresentedSymmetryGroup, T, F>
439where
440 T: ComplexFloat + Lapack + Sync + Send,
441 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + Sync + Send,
442 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
443 F: Clone + Fn(&Point3<f64>) -> T,
444{
445 fn_construct_unitary_group!(
446 construct_unitary_group
449 );
450}
451
452impl<'a, T, F> RealSpaceFunctionRepAnalysisDriver<'a, MagneticRepresentedSymmetryGroup, T, F>
456where
457 T: ComplexFloat + Lapack + Sync + Send,
458 <T as ComplexFloat>::Real: From<f64> + Sync + Send + fmt::LowerExp + fmt::Debug,
459 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
460 F: Clone + Fn(&Point3<f64>) -> T,
461{
462 fn_construct_magnetic_group!(
463 construct_magnetic_group
466 );
467}
468
469#[duplicate_item(
473 duplicate!{
474 [ dtype_nested; [f64]; [Complex<f64>] ]
475 [
476 gtype_ [ UnitaryRepresentedSymmetryGroup ]
477 dtype_ [ dtype_nested ]
478 doc_sub_ [ "Performs representation analysis using a unitary-represented group and stores the result." ]
479 analyse_fn_ [ analyse_representation ]
480 construct_group_ [ self.construct_unitary_group()? ]
481 calc_projections_ [
482 log_subtitle("Real-space function projection decompositions");
483 qsym2_output!("");
484 qsym2_output!(" Projections are defined w.r.t. the following inner product:");
485 qsym2_output!(" {}", real_space_function_orbit.origin().overlap_definition());
486 qsym2_output!("");
487 real_space_function_orbit
488 .projections_to_string(
489 &real_space_function_orbit.calc_projection_compositions()?,
490 params.integrality_threshold,
491 )
492 .log_output_display();
493 qsym2_output!("");
494 ]
495 ]
496 }
497 duplicate!{
498 [ dtype_nested; [f64]; [Complex<f64>] ]
499 [
500 gtype_ [ MagneticRepresentedSymmetryGroup ]
501 dtype_ [ dtype_nested ]
502 doc_sub_ [ "Performs corepresentation analysis using a magnetic-represented group and stores the result." ]
503 analyse_fn_ [ analyse_corepresentation ]
504 construct_group_ [ self.construct_magnetic_group()? ]
505 calc_projections_ [ ]
506 ]
507 }
508)]
509impl<'a, F> RealSpaceFunctionRepAnalysisDriver<'a, gtype_, dtype_, F>
510where
511 F: Clone + Sync + Send + Fn(&Point3<f64>) -> dtype_,
512{
513 #[doc = doc_sub_]
514 fn analyse_fn_(&mut self) -> Result<(), anyhow::Error> {
515 let params = self.parameters;
516 let group = construct_group_;
517 log_cc_transversal(&group);
518 let _ = find_angular_function_representation(&group, self.angular_function_parameters);
519 if group.is_double_group() {
520 let _ = find_spinor_function_representation(&group, self.angular_function_parameters);
521 }
522
523 let mut real_space_function_orbit = RealSpaceFunctionSymmetryOrbit::builder()
524 .origin(self.real_space_function)
525 .group(&group)
526 .integrality_threshold(params.integrality_threshold)
527 .linear_independence_threshold(params.linear_independence_threshold)
528 .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
529 .eigenvalue_comparison_mode(params.eigenvalue_comparison_mode.clone())
530 .build()?;
531 let real_space_function_symmetry = real_space_function_orbit
532 .calc_smat(Some(self.weight), None, params.use_cayley_table)
533 .and_then(|real_space_function_orb| real_space_function_orb.normalise_smat())
534 .map_err(|err| err.to_string())
535 .and_then(|real_space_function_orb| {
536 real_space_function_orb
537 .calc_xmat(false)
538 .map_err(|err| err.to_string())?;
539 if params.write_overlap_eigenvalues
540 && let Some(smat_eigvals) = real_space_function_orb.smat_eigvals.as_ref()
541 {
542 log_overlap_eigenvalues(
543 "Real-space function orbit overlap eigenvalues",
544 smat_eigvals,
545 params.linear_independence_threshold,
546 ¶ms.eigenvalue_comparison_mode,
547 );
548 qsym2_output!("");
549 }
550 real_space_function_orb
551 .analyse_rep()
552 .map_err(|err| err.to_string())
553 });
554
555 {
556 calc_projections_
557 }
558
559 let result = RealSpaceFunctionRepAnalysisResult::builder()
560 .parameters(params)
561 .real_space_function(self.real_space_function)
562 .group(group)
563 .real_space_function_symmetry(real_space_function_symmetry)
564 .build()?;
565 self.result = Some(result);
566
567 Ok(())
568 }
569}
570
571impl<'a, G, T, F> fmt::Display for RealSpaceFunctionRepAnalysisDriver<'a, G, T, F>
579where
580 G: SymmetryGroupProperties + Clone,
581 G::CharTab: SubspaceDecomposable<T>,
582 T: ComplexFloat + Lapack,
583 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
584 F: Clone + Fn(&Point3<f64>) -> T,
585{
586 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
587 write_title(f, "Real-Space Function Symmetry Analysis")?;
588 writeln!(f)?;
589 writeln!(f, "{}", self.parameters)?;
590 Ok(())
591 }
592}
593
594impl<'a, G, T, F> fmt::Debug for RealSpaceFunctionRepAnalysisDriver<'a, G, T, F>
595where
596 G: SymmetryGroupProperties + Clone,
597 G::CharTab: SubspaceDecomposable<T>,
598 T: ComplexFloat + Lapack,
599 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
600 F: Clone + Fn(&Point3<f64>) -> T,
601{
602 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603 writeln!(f, "{self}")
604 }
605}
606
607#[duplicate_item(
611 duplicate!{
612 [ dtype_nested; [f64]; [Complex<f64>] ]
613 [
614 gtype_ [ UnitaryRepresentedSymmetryGroup ]
615 dtype_ [ dtype_nested ]
616 analyse_fn_ [ analyse_representation ]
617 ]
618 }
619 duplicate!{
620 [ dtype_nested; [f64]; [Complex<f64>] ]
621 [
622 gtype_ [ MagneticRepresentedSymmetryGroup ]
623 dtype_ [ dtype_nested ]
624 analyse_fn_ [ analyse_corepresentation ]
625 ]
626 }
627)]
628impl<'a, F> QSym2Driver for RealSpaceFunctionRepAnalysisDriver<'a, gtype_, dtype_, F>
629where
630 F: Clone + Sync + Send + Fn(&Point3<f64>) -> dtype_,
631{
632 type Params = RealSpaceFunctionRepAnalysisParams<f64>;
633
634 type Outcome = RealSpaceFunctionRepAnalysisResult<'a, gtype_, dtype_, F>;
635
636 fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
637 self.result
638 .as_ref()
639 .ok_or_else(|| format_err!("No real-space function analysis results found."))
640 }
641
642 fn run(&mut self) -> Result<(), anyhow::Error> {
643 self.log_output_display();
644 self.analyse_fn_()?;
645 self.result()?.log_output_display();
646 Ok(())
647 }
648}