qsym2/sandbox/bindings/python/representation_analysis/
real_space_function.rs

1//! Sandbox Python bindings for QSym² symmetry analysis of real-space functions.
2
3use std::path::PathBuf;
4
5use duplicate::duplicate_item;
6use nalgebra::Point3;
7use num::Complex;
8use numpy::{PyArray1, PyArray2, PyArrayMethods};
9use pyo3::exceptions::{PyIOError, PyRuntimeError};
10use pyo3::prelude::*;
11use pyo3::types::PyFunction;
12
13use crate::analysis::EigenvalueComparisonMode;
14use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
15use crate::drivers::representation_analysis::{
16    CharacterTableDisplay, MagneticSymmetryAnalysisKind,
17};
18use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
19use crate::drivers::QSym2Driver;
20use crate::io::format::qsym2_output;
21use crate::io::{read_qsym2_binary, QSym2FileType};
22use crate::sandbox::drivers::representation_analysis::real_space_function::{
23    RealSpaceFunctionRepAnalysisDriver, RealSpaceFunctionRepAnalysisParams,
24};
25use crate::sandbox::target::real_space_function::RealSpaceFunction;
26use crate::symmetry::symmetry_group::{
27    MagneticRepresentedSymmetryGroup, UnitaryRepresentedSymmetryGroup,
28};
29use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
30
31// =====================
32// Functions definitions
33// =====================
34
35#[duplicate_item(
36    [
37        dtype_ [ f64 ]
38        doc_sub_ [ "Python-exposed function to perform representation symmetry analysis for real-valued real-space functions and log the result via the `qsym2-output` logger at the `INFO` level." ]
39        func_sub_ [ "* `function` - A Python function callable on three Cartesian coordinates to give a scalar value. Python type: `Callable[[float, float, float], float]`." ]
40        rep_analyse_real_space_function_ [ rep_analyse_real_space_function_real ]
41    ]
42    [
43        dtype_ [ Complex<f64> ]
44        doc_sub_ [ "Python-exposed function to perform representation symmetry analysis for complex-valued real-space functions and log the result via the `qsym2-output` logger at the `INFO` level." ]
45        func_sub_ [ "* `function` - A Python function callable on three Cartesian coordinates to give a scalar value. Python type: `Callable[[float, float, float], complex]`." ]
46        rep_analyse_real_space_function_ [ rep_analyse_real_space_function_complex ]
47    ]
48)]
49#[doc = doc_sub_]
50///
51/// # Arguments
52///
53/// * `inp_sym` - A path to the [`QSym2FileType::Sym`] file containing the symmetry-group detection
54/// result for the system. This will be used to construct abstract groups and character tables for
55/// representation analysis. Python type: `str`.
56#[doc = func_sub_]
57/// * `integrality_threshold` - The threshold for verifying if subspace multiplicities are integral.
58/// Python type: `float`.
59/// * `linear_independence_threshold` - The threshold for determining the linear independence
60/// subspace via the non-zero eigenvalues of the orbit overlap matrix. Python type: `float`.
61/// * `use_magnetic_group` - An option indicating if the magnetic group is to be used for symmetry
62/// analysis, and if so, whether unitary representations or unitary-antiunitary corepresentations
63/// should be used. Python type: `None | MagneticSymmetryAnalysisKind`.
64/// * `use_double_group` - A boolean indicating if the double group of the prevailing symmetry
65/// group is to be used for representation analysis instead. Python type: `bool`.
66/// * `use_cayley_table` - A boolean indicating if the Cayley table for the group, if available,
67/// should be used to speed up the calculation of orbit overlap matrices. Python type: `bool`.
68/// * `symmetry_transformation_kind` - An enumerated type indicating the type of symmetry
69/// transformations to be performed on the origin real-space function to generate the orbit.
70/// Python type: `SymmetryTransformationKind`.
71/// * `eigenvalue_comparison_mode` - An enumerated type indicating the mode of comparison of orbit
72/// overlap eigenvalues with the specified `linear_independence_threshold`.
73/// Python type: `EigenvalueComparisonMode`.
74/// * `grid_points` - The grid points at which the real-space function is evaluated specified as a
75/// $`3 \times N`$ array where $`N`$ is the number of points. Python type: `numpy.2darray[float]`.
76/// * `weight` - The weight to be used in the computation of overlaps between real-space functions
77/// specified as a one-dimensional array. The number of weight values must match the number of grid
78/// points. Python type: `numpy.1darray[float]`.
79/// * `write_overlap_eigenvalues` - A boolean indicating if the eigenvalues of the real-space
80/// function orbit overlap matrix are to be written to the output. Python type: `bool`.
81/// * `write_character_table` - A boolean indicating if the character table of the prevailing
82/// symmetry group is to be printed out. Python type: `bool`.
83/// * `infinite_order_to_finite` - The finite order with which infinite-order generators are to be
84/// interpreted to form a finite subgroup of the prevailing infinite group. This finite subgroup
85/// will be used for symmetry analysis. Python type: `Optional[int]`.
86/// * `angular_function_integrality_threshold` - The threshold for verifying if subspace
87/// multiplicities are integral for the symmetry analysis of angular functions. Python type:
88/// `float`.
89/// * `angular_function_linear_independence_threshold` - The threshold for determining the linear
90/// independence subspace via the non-zero eigenvalues of the orbit overlap matrix for the symmetry
91/// analysis of angular functions. Python type: `float`.
92/// * `angular_function_max_angular_momentum` - The maximum angular momentum order to be used in
93/// angular function symmetry analysis. Python type: `int`.
94#[pyfunction]
95#[pyo3(signature = (
96    inp_sym,
97    function,
98    integrality_threshold,
99    linear_independence_threshold,
100    use_magnetic_group,
101    use_double_group,
102    use_cayley_table,
103    symmetry_transformation_kind,
104    eigenvalue_comparison_mode,
105    grid_points,
106    weight,
107    write_overlap_eigenvalues=true,
108    write_character_table=true,
109    infinite_order_to_finite=None,
110    angular_function_integrality_threshold=1e-7,
111    angular_function_linear_independence_threshold=1e-7,
112    angular_function_max_angular_momentum=2
113))]
114pub fn rep_analyse_real_space_function_(
115    py: Python<'_>,
116    inp_sym: PathBuf,
117    function: Py<PyFunction>,
118    integrality_threshold: f64,
119    linear_independence_threshold: f64,
120    use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
121    use_double_group: bool,
122    use_cayley_table: bool,
123    symmetry_transformation_kind: SymmetryTransformationKind,
124    eigenvalue_comparison_mode: EigenvalueComparisonMode,
125    grid_points: Bound<'_, PyArray2<f64>>,
126    weight: Bound<'_, PyArray1<dtype_>>,
127    write_overlap_eigenvalues: bool,
128    write_character_table: bool,
129    infinite_order_to_finite: Option<u32>,
130    angular_function_integrality_threshold: f64,
131    angular_function_linear_independence_threshold: f64,
132    angular_function_max_angular_momentum: u32,
133) -> PyResult<()> {
134    let pd_res: SymmetryGroupDetectionResult =
135        read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
136            .map_err(|err| PyIOError::new_err(err.to_string()))?;
137
138    let mut file_name = inp_sym.to_path_buf();
139    file_name.set_extension(QSym2FileType::Sym.ext());
140    qsym2_output!(
141        "Symmetry-group detection results read in from {}.",
142        file_name.display(),
143    );
144    qsym2_output!("");
145
146    let afa_params = AngularFunctionRepAnalysisParams::builder()
147        .integrality_threshold(angular_function_integrality_threshold)
148        .linear_independence_threshold(angular_function_linear_independence_threshold)
149        .max_angular_momentum(angular_function_max_angular_momentum)
150        .build()
151        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
152    let real_space_function_params = RealSpaceFunctionRepAnalysisParams::<f64>::builder()
153        .integrality_threshold(integrality_threshold)
154        .linear_independence_threshold(linear_independence_threshold)
155        .use_magnetic_group(use_magnetic_group.clone())
156        .use_double_group(use_double_group)
157        .use_cayley_table(use_cayley_table)
158        .symmetry_transformation_kind(symmetry_transformation_kind)
159        .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
160        .write_overlap_eigenvalues(write_overlap_eigenvalues)
161        .write_character_table(if write_character_table {
162            Some(CharacterTableDisplay::Symbolic)
163        } else {
164            None
165        })
166        .infinite_order_to_finite(infinite_order_to_finite)
167        .build()
168        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
169
170    let weight = weight.to_owned_array();
171    let grid_array = grid_points.to_owned_array();
172    if grid_array.shape()[0] != 3 {
173        return Err(PyRuntimeError::new_err(
174            "The grid point array does not have the expected dimensions of 3 × N.",
175        ));
176    }
177    let grid_points = grid_array
178        .columns()
179        .into_iter()
180        .map(|col| Point3::new(col[0], col[1], col[2]))
181        .collect::<Vec<_>>();
182
183    let real_space_function = RealSpaceFunction::<dtype_, _>::builder()
184        .function(|pt| {
185            Python::with_gil(|py_inner| {
186                let res = function.call1(py_inner, (pt.x, pt.y, pt.z)).expect(
187                    "Unable to apply the real-space function on the specified coordinates.",
188                );
189                res.extract::<dtype_>(py_inner)
190                    .expect("Unable to extract the result from the real-space function call.")
191            })
192        })
193        .grid_points(grid_points)
194        .build()
195        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
196
197    match &use_magnetic_group {
198        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
199            let mut real_space_function_driver = RealSpaceFunctionRepAnalysisDriver::<
200                MagneticRepresentedSymmetryGroup,
201                dtype_,
202                _,
203            >::builder()
204            .parameters(&real_space_function_params)
205            .angular_function_parameters(&afa_params)
206            .real_space_function(&real_space_function)
207            .weight(&weight)
208            .symmetry_group(&pd_res)
209            .build()
210            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
211            py.allow_threads(|| {
212                real_space_function_driver
213                    .run()
214                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
215            })?
216        }
217        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
218            let mut real_space_function_driver = RealSpaceFunctionRepAnalysisDriver::<
219                UnitaryRepresentedSymmetryGroup,
220                dtype_,
221                _,
222            >::builder()
223            .parameters(&real_space_function_params)
224            .angular_function_parameters(&afa_params)
225            .real_space_function(&real_space_function)
226            .weight(&weight)
227            .symmetry_group(&pd_res)
228            .build()
229            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
230            py.allow_threads(|| {
231                real_space_function_driver
232                    .run()
233                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
234            })?
235        }
236    };
237
238    Ok(())
239}