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#[allow(clippy::too_many_arguments)]
95#[pyfunction]
96#[pyo3(signature = (
97    inp_sym,
98    function,
99    integrality_threshold,
100    linear_independence_threshold,
101    use_magnetic_group,
102    use_double_group,
103    use_cayley_table,
104    symmetry_transformation_kind,
105    eigenvalue_comparison_mode,
106    grid_points,
107    weight,
108    write_overlap_eigenvalues=true,
109    write_character_table=true,
110    infinite_order_to_finite=None,
111    angular_function_integrality_threshold=1e-7,
112    angular_function_linear_independence_threshold=1e-7,
113    angular_function_max_angular_momentum=2
114))]
115pub fn rep_analyse_real_space_function_(
116    py: Python<'_>,
117    inp_sym: PathBuf,
118    function: Py<PyFunction>,
119    integrality_threshold: f64,
120    linear_independence_threshold: f64,
121    use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
122    use_double_group: bool,
123    use_cayley_table: bool,
124    symmetry_transformation_kind: SymmetryTransformationKind,
125    eigenvalue_comparison_mode: EigenvalueComparisonMode,
126    grid_points: Bound<'_, PyArray2<f64>>,
127    weight: Bound<'_, PyArray1<dtype_>>,
128    write_overlap_eigenvalues: bool,
129    write_character_table: bool,
130    infinite_order_to_finite: Option<u32>,
131    angular_function_integrality_threshold: f64,
132    angular_function_linear_independence_threshold: f64,
133    angular_function_max_angular_momentum: u32,
134) -> PyResult<()> {
135    let pd_res: SymmetryGroupDetectionResult =
136        read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
137            .map_err(|err| PyIOError::new_err(err.to_string()))?;
138
139    let mut file_name = inp_sym.to_path_buf();
140    file_name.set_extension(QSym2FileType::Sym.ext());
141    qsym2_output!(
142        "Symmetry-group detection results read in from {}.",
143        file_name.display(),
144    );
145    qsym2_output!("");
146
147    let afa_params = AngularFunctionRepAnalysisParams::builder()
148        .integrality_threshold(angular_function_integrality_threshold)
149        .linear_independence_threshold(angular_function_linear_independence_threshold)
150        .max_angular_momentum(angular_function_max_angular_momentum)
151        .build()
152        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
153    let real_space_function_params = RealSpaceFunctionRepAnalysisParams::<f64>::builder()
154        .integrality_threshold(integrality_threshold)
155        .linear_independence_threshold(linear_independence_threshold)
156        .use_magnetic_group(use_magnetic_group.clone())
157        .use_double_group(use_double_group)
158        .use_cayley_table(use_cayley_table)
159        .symmetry_transformation_kind(symmetry_transformation_kind)
160        .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
161        .write_overlap_eigenvalues(write_overlap_eigenvalues)
162        .write_character_table(if write_character_table {
163            Some(CharacterTableDisplay::Symbolic)
164        } else {
165            None
166        })
167        .infinite_order_to_finite(infinite_order_to_finite)
168        .build()
169        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
170
171    let weight = weight.to_owned_array();
172    let grid_array = grid_points.to_owned_array();
173    if grid_array.shape()[0] != 3 {
174        return Err(PyRuntimeError::new_err(
175            "The grid point array does not have the expected dimensions of 3 × N.",
176        ));
177    }
178    let grid_points = grid_array
179        .columns()
180        .into_iter()
181        .map(|col| Point3::new(col[0], col[1], col[2]))
182        .collect::<Vec<_>>();
183
184    let real_space_function = RealSpaceFunction::<dtype_, _>::builder()
185        .function(|pt| {
186            Python::attach(|py_inner| {
187                let res = function.call1(py_inner, (pt.x, pt.y, pt.z)).expect(
188                    "Unable to apply the real-space function on the specified coordinates.",
189                );
190                res.extract::<dtype_>(py_inner)
191                    .expect("Unable to extract the result from the real-space function call.")
192            })
193        })
194        .grid_points(grid_points)
195        .build()
196        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
197
198    match &use_magnetic_group {
199        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
200            let mut real_space_function_driver = RealSpaceFunctionRepAnalysisDriver::<
201                MagneticRepresentedSymmetryGroup,
202                dtype_,
203                _,
204            >::builder()
205            .parameters(&real_space_function_params)
206            .angular_function_parameters(&afa_params)
207            .real_space_function(&real_space_function)
208            .weight(&weight)
209            .symmetry_group(&pd_res)
210            .build()
211            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
212            py.detach(|| {
213                real_space_function_driver
214                    .run()
215                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
216            })?
217        }
218        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
219            let mut real_space_function_driver = RealSpaceFunctionRepAnalysisDriver::<
220                UnitaryRepresentedSymmetryGroup,
221                dtype_,
222                _,
223            >::builder()
224            .parameters(&real_space_function_params)
225            .angular_function_parameters(&afa_params)
226            .real_space_function(&real_space_function)
227            .weight(&weight)
228            .symmetry_group(&pd_res)
229            .build()
230            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
231            py.detach(|| {
232                real_space_function_driver
233                    .run()
234                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
235            })?
236        }
237    };
238
239    Ok(())
240}