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 log_overlap_eigenvalues, EigenvalueComparisonMode, Orbit, Overlap, ProjectionDecomposition,
18 RepAnalysis,
19};
20use crate::chartab::chartab_group::CharacterProperties;
21use crate::chartab::SubspaceDecomposable;
22use crate::drivers::representation_analysis::angular_function::{
23 find_angular_function_representation, find_spinor_function_representation,
24 AngularFunctionRepAnalysisParams,
25};
26use crate::drivers::representation_analysis::{
27 fn_construct_magnetic_group, fn_construct_unitary_group, log_cc_transversal,
28 CharacterTableDisplay, MagneticSymmetryAnalysisKind,
29};
30use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
31use crate::drivers::QSym2Driver;
32use crate::group::{GroupProperties, MagneticRepresentedGroup, UnitaryRepresentedGroup};
33use crate::io::format::{
34 log_subtitle, nice_bool, qsym2_output, write_subtitle, write_title, QSym2Output,
35};
36use crate::sandbox::target::real_space_function::real_space_function_analysis::RealSpaceFunctionSymmetryOrbit;
37use crate::sandbox::target::real_space_function::RealSpaceFunction;
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 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
325#[derive(Clone, Builder)]
335#[builder(build_fn(validate = "Self::validate"))]
336pub struct RealSpaceFunctionRepAnalysisDriver<'a, G, T, F>
337where
338 G: SymmetryGroupProperties + Clone,
339 G::CharTab: SubspaceDecomposable<T>,
340 T: ComplexFloat + Lapack,
341 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
342 F: Clone + Fn(&Point3<f64>) -> T,
343{
344 parameters: &'a RealSpaceFunctionRepAnalysisParams<<T as ComplexFloat>::Real>,
346
347 real_space_function: &'a RealSpaceFunction<T, F>,
349
350 symmetry_group: &'a SymmetryGroupDetectionResult,
353
354 weight: &'a Array1<T>,
356
357 angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
359
360 #[builder(setter(skip), default = "None")]
362 result: Option<RealSpaceFunctionRepAnalysisResult<'a, G, T, F>>,
363}
364
365impl<'a, G, T, F> RealSpaceFunctionRepAnalysisDriverBuilder<'a, G, T, F>
366where
367 G: SymmetryGroupProperties + Clone,
368 G::CharTab: SubspaceDecomposable<T>,
369 T: ComplexFloat + Lapack,
370 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
371 F: Clone + Fn(&Point3<f64>) -> T,
372{
373 fn validate(&self) -> Result<(), String> {
374 let _ = self
375 .real_space_function
376 .ok_or("No real-space function specified.".to_string())?;
377
378 let params = self.parameters.ok_or(
379 "No real-space function representation analysis parameters found.".to_string(),
380 )?;
381
382 let sym_res = self
383 .symmetry_group
384 .ok_or("No symmetry group information found.".to_string())?;
385
386 let sym = if params.use_magnetic_group.is_some() {
387 sym_res
388 .magnetic_symmetry
389 .as_ref()
390 .ok_or("Magnetic symmetry requested for representation analysis, but no magnetic symmetry found.")?
391 } else {
392 &sym_res.unitary_symmetry
393 };
394
395 if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
396 Err(
397 format!(
398 "Representation analysis cannot be performed using the entirety of the infinite group `{}`. \
399 Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
400 sym.group_name.as_ref().expect("No symmetry group name found.")
401 )
402 )
403 } else {
404 Ok(())
405 }
406 }
407}
408
409impl<'a, G, T, F> RealSpaceFunctionRepAnalysisDriver<'a, G, T, F>
417where
418 G: SymmetryGroupProperties + Clone,
419 G::CharTab: SubspaceDecomposable<T>,
420 T: ComplexFloat + Lapack,
421 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
422 F: Clone + Fn(&Point3<f64>) -> T,
423{
424 pub fn builder() -> RealSpaceFunctionRepAnalysisDriverBuilder<'a, G, T, F> {
426 RealSpaceFunctionRepAnalysisDriverBuilder::default()
427 }
428}
429
430impl<'a, T, F> RealSpaceFunctionRepAnalysisDriver<'a, UnitaryRepresentedSymmetryGroup, T, F>
434where
435 T: ComplexFloat + Lapack + Sync + Send,
436 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + Sync + Send,
437 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
438 F: Clone + Fn(&Point3<f64>) -> T,
439{
440 fn_construct_unitary_group!(
441 construct_unitary_group
444 );
445}
446
447impl<'a, T, F> RealSpaceFunctionRepAnalysisDriver<'a, MagneticRepresentedSymmetryGroup, T, F>
451where
452 T: ComplexFloat + Lapack + Sync + Send,
453 <T as ComplexFloat>::Real: From<f64> + Sync + Send + fmt::LowerExp + fmt::Debug,
454 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
455 F: Clone + Fn(&Point3<f64>) -> T,
456{
457 fn_construct_magnetic_group!(
458 construct_magnetic_group
461 );
462}
463
464#[duplicate_item(
468 duplicate!{
469 [ dtype_nested; [f64]; [Complex<f64>] ]
470 [
471 gtype_ [ UnitaryRepresentedSymmetryGroup ]
472 dtype_ [ dtype_nested ]
473 doc_sub_ [ "Performs representation analysis using a unitary-represented group and stores the result." ]
474 analyse_fn_ [ analyse_representation ]
475 construct_group_ [ self.construct_unitary_group()? ]
476 calc_projections_ [
477 log_subtitle("Real-space function projection decompositions");
478 qsym2_output!("");
479 qsym2_output!(" Projections are defined w.r.t. the following inner product:");
480 qsym2_output!(" {}", real_space_function_orbit.origin().overlap_definition());
481 qsym2_output!("");
482 real_space_function_orbit
483 .projections_to_string(
484 &real_space_function_orbit.calc_projection_compositions()?,
485 params.integrality_threshold,
486 )
487 .log_output_display();
488 qsym2_output!("");
489 ]
490 ]
491 }
492 duplicate!{
493 [ dtype_nested; [f64]; [Complex<f64>] ]
494 [
495 gtype_ [ MagneticRepresentedSymmetryGroup ]
496 dtype_ [ dtype_nested ]
497 doc_sub_ [ "Performs corepresentation analysis using a magnetic-represented group and stores the result." ]
498 analyse_fn_ [ analyse_corepresentation ]
499 construct_group_ [ self.construct_magnetic_group()? ]
500 calc_projections_ [ ]
501 ]
502 }
503)]
504impl<'a, F> RealSpaceFunctionRepAnalysisDriver<'a, gtype_, dtype_, F>
505where
506 F: Clone + Sync + Send + Fn(&Point3<f64>) -> dtype_,
507{
508 #[doc = doc_sub_]
509 fn analyse_fn_(&mut self) -> Result<(), anyhow::Error> {
510 let params = self.parameters;
511 let group = construct_group_;
512 log_cc_transversal(&group);
513 let _ = find_angular_function_representation(&group, self.angular_function_parameters);
514 if group.is_double_group() {
515 let _ = find_spinor_function_representation(&group, self.angular_function_parameters);
516 }
517
518 let mut real_space_function_orbit = RealSpaceFunctionSymmetryOrbit::builder()
519 .origin(self.real_space_function)
520 .group(&group)
521 .integrality_threshold(params.integrality_threshold)
522 .linear_independence_threshold(params.linear_independence_threshold)
523 .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
524 .eigenvalue_comparison_mode(params.eigenvalue_comparison_mode.clone())
525 .build()?;
526 let real_space_function_symmetry = real_space_function_orbit
527 .calc_smat(Some(self.weight), None, params.use_cayley_table)
528 .and_then(|real_space_function_orb| real_space_function_orb.normalise_smat())
529 .map_err(|err| err.to_string())
530 .and_then(|real_space_function_orb| {
531 real_space_function_orb
532 .calc_xmat(false)
533 .map_err(|err| err.to_string())?;
534 if params.write_overlap_eigenvalues {
535 if let Some(smat_eigvals) = real_space_function_orb.smat_eigvals.as_ref() {
536 log_overlap_eigenvalues(
537 "Real-space function orbit overlap eigenvalues",
538 smat_eigvals,
539 params.linear_independence_threshold,
540 ¶ms.eigenvalue_comparison_mode,
541 );
542 qsym2_output!("");
543 }
544 }
545 real_space_function_orb
546 .analyse_rep()
547 .map_err(|err| err.to_string())
548 });
549
550 {
551 calc_projections_
552 }
553
554 let result = RealSpaceFunctionRepAnalysisResult::builder()
555 .parameters(params)
556 .real_space_function(self.real_space_function)
557 .group(group)
558 .real_space_function_symmetry(real_space_function_symmetry)
559 .build()?;
560 self.result = Some(result);
561
562 Ok(())
563 }
564}
565
566impl<'a, G, T, F> fmt::Display for RealSpaceFunctionRepAnalysisDriver<'a, G, T, F>
574where
575 G: SymmetryGroupProperties + Clone,
576 G::CharTab: SubspaceDecomposable<T>,
577 T: ComplexFloat + Lapack,
578 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
579 F: Clone + Fn(&Point3<f64>) -> T,
580{
581 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582 write_title(f, "Real-Space Function Symmetry Analysis")?;
583 writeln!(f)?;
584 writeln!(f, "{}", self.parameters)?;
585 Ok(())
586 }
587}
588
589impl<'a, G, T, F> fmt::Debug for RealSpaceFunctionRepAnalysisDriver<'a, G, T, F>
590where
591 G: SymmetryGroupProperties + Clone,
592 G::CharTab: SubspaceDecomposable<T>,
593 T: ComplexFloat + Lapack,
594 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
595 F: Clone + Fn(&Point3<f64>) -> T,
596{
597 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
598 writeln!(f, "{self}")
599 }
600}
601
602#[duplicate_item(
606 duplicate!{
607 [ dtype_nested; [f64]; [Complex<f64>] ]
608 [
609 gtype_ [ UnitaryRepresentedSymmetryGroup ]
610 dtype_ [ dtype_nested ]
611 analyse_fn_ [ analyse_representation ]
612 ]
613 }
614 duplicate!{
615 [ dtype_nested; [f64]; [Complex<f64>] ]
616 [
617 gtype_ [ MagneticRepresentedSymmetryGroup ]
618 dtype_ [ dtype_nested ]
619 analyse_fn_ [ analyse_corepresentation ]
620 ]
621 }
622)]
623impl<'a, F> QSym2Driver for RealSpaceFunctionRepAnalysisDriver<'a, gtype_, dtype_, F>
624where
625 F: Clone + Sync + Send + Fn(&Point3<f64>) -> dtype_,
626{
627 type Params = RealSpaceFunctionRepAnalysisParams<f64>;
628
629 type Outcome = RealSpaceFunctionRepAnalysisResult<'a, gtype_, dtype_, F>;
630
631 fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
632 self.result
633 .as_ref()
634 .ok_or_else(|| format_err!("No real-space function analysis results found."))
635 }
636
637 fn run(&mut self) -> Result<(), anyhow::Error> {
638 self.log_output_display();
639 self.analyse_fn_()?;
640 self.result()?.log_output_display();
641 Ok(())
642 }
643}