qsym2/interfaces/binaries/
mod.rs

1//! QSym² interface with binary data files.
2
3use std::path::PathBuf;
4
5use anyhow::{Context, format_err};
6use byteorder::{BigEndian, LittleEndian};
7use derive_builder::Builder;
8use itertools::Itertools;
9use ndarray::{Array1, Array2, Array4, ShapeBuilder};
10use serde::{Deserialize, Serialize};
11
12use crate::angmom::spinor_rotation_3d::SpinConstraint;
13use crate::drivers::QSym2Driver;
14use crate::drivers::representation_analysis::MagneticSymmetryAnalysisKind;
15use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
16use crate::drivers::representation_analysis::slater_determinant::{
17    SlaterDeterminantRepAnalysisDriver, SlaterDeterminantRepAnalysisParams,
18};
19use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionDriver;
20use crate::interfaces::input::SymmetryGroupDetectionInputKind;
21use crate::interfaces::input::analysis::SlaterDeterminantSourceHandle;
22use crate::interfaces::input::ao_basis::InputBasisAngularOrder;
23use crate::io::numeric::NumericReader;
24use crate::io::{QSym2FileType, read_qsym2_binary};
25use crate::symmetry::symmetry_group::{
26    MagneticRepresentedSymmetryGroup, UnitaryRepresentedSymmetryGroup,
27};
28use crate::target::determinant::SlaterDeterminant;
29
30#[cfg(test)]
31#[path = "binaries_tests.rs"]
32mod binaries_tests;
33
34// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35// Input target: Slater determinant; source: binaries
36// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37
38/// Serialisable/deserialisable structure containing control parameters for acquiring Slater
39/// determinant(s) from a custom specification.
40#[derive(Clone, Builder, Serialize, Deserialize)]
41pub struct BinariesSlaterDeterminantSource {
42    /// Path to an XYZ file containing the molecular geometry.
43    pub xyz: PathBuf,
44
45    /// Path to a binary file containing the two-centre atomic-orbital spatial overlap matrix.
46    pub sao: PathBuf,
47
48    /// Optional path to a binary file containing the four-centre atomic-orbital spatial overlap
49    /// matrix. This is only required for density symmetry analysis.
50    #[builder(default = "None")]
51    pub sao_4c: Option<PathBuf>,
52
53    /// Paths to binary files containing molecular-orbital coefficient matrices for different spin
54    /// spaces.
55    pub coefficients: Vec<PathBuf>,
56
57    /// Paths to binary files containing occupation numbers for the molecular orbitals.
58    pub occupations: Vec<PathBuf>,
59
60    /// Specifications of basis angular order information, one for each explicit component per
61    /// coefficient matrix.
62    pub baos: Vec<InputBasisAngularOrder>,
63
64    /// Specification of spin constraint.
65    pub spin_constraint: SpinConstraint,
66
67    /// Specification of the order matrix elements are packed in binary files.
68    pub matrix_order: MatrixOrder,
69
70    /// Specification of the byte order numerical values are stored in binary files.
71    pub byte_order: ByteOrder,
72}
73
74impl BinariesSlaterDeterminantSource {
75    /// Returns a builder to construct a structure for handling binaries Slater determinant
76    /// source.
77    pub fn builder() -> BinariesSlaterDeterminantSourceBuilder {
78        BinariesSlaterDeterminantSourceBuilder::default()
79    }
80}
81
82impl Default for BinariesSlaterDeterminantSource {
83    fn default() -> Self {
84        BinariesSlaterDeterminantSource::builder()
85            .xyz(PathBuf::from("path/to/xyz"))
86            .sao(PathBuf::from("path/to/2c/ao/overlap/matrix"))
87            .sao_4c(None)
88            .coefficients(vec![
89                PathBuf::from("path/to/alpha/coeffs"),
90                PathBuf::from("path/to/beta/coeffs"),
91            ])
92            .occupations(vec![
93                PathBuf::from("path/to/alpha/occupations"),
94                PathBuf::from("path/to/beta/occupations"),
95            ])
96            .baos(vec![InputBasisAngularOrder::default()])
97            .spin_constraint(SpinConstraint::Unrestricted(2, false))
98            .matrix_order(MatrixOrder::default())
99            .byte_order(ByteOrder::default())
100            .build()
101            .expect("Unable to build a default `BinariesSlaterDeterminantSource`.")
102    }
103}
104
105impl SlaterDeterminantSourceHandle for BinariesSlaterDeterminantSource {
106    type Outcome = (String, String);
107
108    fn sd_source_handle(
109        &self,
110        pd_params_inp: &SymmetryGroupDetectionInputKind,
111        afa_params: &AngularFunctionRepAnalysisParams,
112        sda_params: &SlaterDeterminantRepAnalysisParams<f64>,
113    ) -> Result<Self::Outcome, anyhow::Error> {
114        let pd_res = match pd_params_inp {
115            SymmetryGroupDetectionInputKind::Parameters(pd_params) => {
116                let mut pd_driver = SymmetryGroupDetectionDriver::builder()
117                    .parameters(pd_params)
118                    .xyz(Some(self.xyz.clone()))
119                    .build()
120                    .with_context(|| "Unable to construct a symmetry-group detection driver when handling custom Slater determinant source")?;
121                pd_driver.run().with_context(|| {
122                    "Unable to run the symmetry-group detection driver successfully when handling custom Slater determinant source"
123                })?;
124                pd_driver
125                    .result()
126                    .with_context(|| "Unable to retrieve the symmetry-group detection result when handling custom Slater determinant source")?
127                    .clone()
128            }
129            SymmetryGroupDetectionInputKind::FromFile(pd_res_file) => read_qsym2_binary(
130                pd_res_file,
131                QSym2FileType::Sym,
132            )
133            .with_context(|| {
134                format!(
135                    "Unable to read `{}.qsym2.sym` when handling custom Slater determinant source",
136                    pd_res_file.display()
137                )
138            })?,
139        };
140        let mol = &pd_res.pre_symmetry.recentred_molecule;
141        let baos = self.baos.iter().map(|bao| bao.to_basis_angular_order(mol)
142            .with_context(|| "Unable to digest the input basis angular order information when handling custom Slater determinant source")).collect::<Result<Vec<_>, _>>()?;
143        let nfuncs_tot = baos.iter().map(|bao| bao.n_funcs()).sum::<usize>();
144
145        let sao_v = match self.byte_order {
146            ByteOrder::LittleEndian => {
147                NumericReader::<_, LittleEndian, f64>::from_file(&self.sao)
148                    .with_context(|| {
149                        "Unable to read the specified two-centre SAO file when handling custom Slater determinant source"
150                    })?.collect::<Vec<_>>()
151            }
152            ByteOrder::BigEndian => {
153                NumericReader::<_, BigEndian, f64>::from_file(&self.sao)
154                    .with_context(|| {
155                        "Unable to read the specified two-centre SAO file when handling custom Slater determinant source"
156                    })?.collect::<Vec<_>>()
157            }
158        };
159        let sao = match self.matrix_order {
160            MatrixOrder::RowMajor => Array2::from_shape_vec((nfuncs_tot, nfuncs_tot), sao_v)
161                .with_context(|| {
162                    "Unable to construct an AO overlap matrix from the read-in row-major binary file when handling custom Slater determinant source"
163                })?,
164            MatrixOrder::ColMajor => Array2::from_shape_vec((nfuncs_tot, nfuncs_tot).f(), sao_v)
165                .with_context(|| {
166                    "Unable to construct an AO overlap matrix from the read-in column-major binary file when handling custom Slater determinant source"
167                })?,
168        };
169
170        let sao_4c = if let Some(sao_4c_path) = self.sao_4c.as_ref() {
171            let sao_4c_v = match self.byte_order {
172                ByteOrder::LittleEndian => {
173                    NumericReader::<_, LittleEndian, f64>::from_file(sao_4c_path)
174                        .with_context(|| {
175                            "Unable to read the specified four-centre SAO file when handling custom Slater determinant source"
176                        })?.collect::<Vec<_>>()
177                }
178                ByteOrder::BigEndian => {
179                    NumericReader::<_, BigEndian, f64>::from_file(sao_4c_path)
180                        .with_context(|| {
181                            "Unable to read the specified four-centre SAO file when handling custom Slater determinant source"
182                        })?.collect::<Vec<_>>()
183                }
184            };
185            let sao_4c = match self.matrix_order {
186                MatrixOrder::RowMajor => Array4::from_shape_vec((nfuncs_tot, nfuncs_tot, nfuncs_tot, nfuncs_tot), sao_4c_v)
187                    .with_context(|| {
188                        "Unable to construct a four-centre AO overlap matrix from the read-in row-major binary file when handling custom Slater determinant source"
189                    })?,
190                MatrixOrder::ColMajor => Array4::from_shape_vec((nfuncs_tot, nfuncs_tot, nfuncs_tot, nfuncs_tot).f(), sao_4c_v)
191                    .with_context(|| {
192                        "Unable to construct a four-centre AO overlap matrix from the read-in column-major binary file when handling custom Slater determinant source"
193                    })?,
194            };
195            Some(sao_4c)
196        } else {
197            None
198        };
199
200        let cs_v = match self.byte_order {
201            ByteOrder::LittleEndian => self
202                .coefficients
203                .iter()
204                .map(|c_path| {
205                    NumericReader::<_, LittleEndian, f64>::from_file(c_path)
206                        .map(|r| r.collect::<Vec<_>>())
207                })
208                .collect::<Result<Vec<_>, _>>()
209                .with_context(|| {
210                    "Unable to read the specified coefficient binary file(s) when handling custom Slater determinant source"
211                })?,
212            ByteOrder::BigEndian => self
213                .coefficients
214                .iter()
215                .map(|c_path| {
216                    NumericReader::<_, BigEndian, f64>::from_file(c_path)
217                        .map(|r| r.collect::<Vec<_>>())
218                })
219                .collect::<Result<Vec<_>, _>>()
220                .with_context(|| {
221                    "Unable to read the specified coefficient binary file(s) when handling custom Slater determinant source"
222                })?,
223        };
224        let cs = match self.matrix_order {
225            MatrixOrder::RowMajor => cs_v
226                .into_iter()
227                .map(|c_v| {
228                    let nmo = c_v.len().div_euclid(nfuncs_tot);
229                    Array2::from_shape_vec((nfuncs_tot, nmo), c_v)
230                })
231                .collect::<Result<Vec<_>, _>>()
232                .with_context(|| {
233                    "Unable to construct coefficient matrix (matrices) from the read-in row-major binary file(s) when handling custom Slater determinant source"
234                })?,
235
236            MatrixOrder::ColMajor => cs_v
237                .into_iter()
238                .map(|c_v| {
239                    let nmo = c_v.len().div_euclid(nfuncs_tot);
240                    Array2::from_shape_vec((nfuncs_tot, nmo).f(), c_v)
241                })
242                .collect::<Result<Vec<_>, _>>()
243                .with_context(|| {
244                    "Unable to construct coefficient matrix (matrices) from the read-in column-major binary file(s) when handling custom Slater determinant source"
245                })?,
246        };
247
248        let occs = match self.byte_order {
249            ByteOrder::LittleEndian => self
250                .occupations
251                .iter()
252                .map(|occ_path| {
253                    Ok::<_, anyhow::Error>(Array1::from_vec(
254                        NumericReader::<_, LittleEndian, f64>::from_file(occ_path)
255                            .map(|r| r.collect::<Vec<f64>>())?,
256                    ))
257                })
258                .collect::<Result<Vec<_>, _>>()
259                .with_context(|| {
260                    "Unable to read the specified occupation binary file(s) when handling custom Slater determinant source"
261                })?,
262            ByteOrder::BigEndian => self
263                .occupations
264                .iter()
265                .map(|occ_path| {
266                    Ok::<_, anyhow::Error>(Array1::from_vec(
267                        NumericReader::<_, BigEndian, f64>::from_file(occ_path)
268                            .map(|r| r.collect::<Vec<f64>>())?,
269                    ))
270                })
271                .collect::<Result<Vec<_>, _>>()
272                .with_context(|| {
273                    "Unable to read occupation binary file(s) when handling custom Slater determinant source"
274                })?,
275        };
276
277        let det = SlaterDeterminant::<f64, SpinConstraint>::builder()
278            .coefficients(&cs)
279            .occupations(&occs)
280            .baos(baos.iter().collect_vec())
281            .mol(mol)
282            .structure_constraint(self.spin_constraint.clone())
283            .complex_symmetric(false)
284            .threshold(sda_params.linear_independence_threshold)
285            .build()
286            .with_context(|| "Failed to construct a Slater determinant when handling custom Slater determinant source")?;
287
288        match &sda_params.use_magnetic_group {
289            Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
290                let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
291                    MagneticRepresentedSymmetryGroup,
292                    f64,
293                    SpinConstraint,
294                >::builder()
295                .parameters(sda_params)
296                .angular_function_parameters(afa_params)
297                .determinant(&det)
298                .sao(&sao)
299                .sao_spatial_4c(sao_4c.as_ref())
300                .symmetry_group(&pd_res)
301                .build()
302                .with_context(|| {
303                    "Failed to construct a Slater determinant corepresentation analysis driver when handling custom Slater determinant source"
304                })?;
305                sda_driver
306                    .run()
307                    .with_context(|| {
308                        "Failed to execute the Slater determinant corepresentation analysis driver successfully when handling custom Slater determinant source"
309                    })?;
310                let group_name = pd_res
311                    .magnetic_symmetry
312                    .as_ref()
313                    .and_then(|magsym| magsym.group_name.clone())
314                    .ok_or(format_err!("Magnetic group name not found when handling custom Slater determinant source."))?;
315                let sym = sda_driver
316                    .result()
317                    .with_context(|| {
318                        "Failed to obtain corepresentation analysis result when handling custom Slater determinant source"
319                    })?
320                    .determinant_symmetry()
321                    .as_ref()
322                    .map_err(|err| format_err!(err.clone()))
323                    .with_context(|| {
324                        "Failed to obtain determinant symmetry from corepresentation analysis result when handling custom Slater determinant source"
325                    })?
326                    .to_string();
327                Ok((group_name, sym))
328            }
329            Some(MagneticSymmetryAnalysisKind::Representation) | None => {
330                let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
331                    UnitaryRepresentedSymmetryGroup,
332                    f64,
333                    SpinConstraint,
334                >::builder()
335                .parameters(sda_params)
336                .angular_function_parameters(afa_params)
337                .determinant(&det)
338                .sao(&sao)
339                .sao_spatial_4c(sao_4c.as_ref())
340                .symmetry_group(&pd_res)
341                .build()
342                .with_context(|| {
343                    "Failed to construct a Slater determinant representation analysis driver when handling custom Slater determinant source"
344                })?;
345                sda_driver
346                    .run()
347                    .with_context(|| {
348                        "Failed to execute the Slater determinant representation analysis driver successfully when handling custom Slater determinant source"
349                    })?;
350                let group_name = if sda_params.use_magnetic_group.is_none() {
351                    pd_res
352                        .unitary_symmetry
353                        .group_name
354                        .as_ref()
355                        .ok_or(format_err!("Unitary group name not found when handling custom Slater determinant source."))?.clone()
356                } else {
357                    pd_res
358                        .magnetic_symmetry
359                        .as_ref()
360                        .and_then(|magsym| magsym.group_name.clone())
361                        .ok_or(format_err!("Magnetic group name not found when handling custom Slater determinant source."))?
362                };
363                let sym = sda_driver
364                    .result()
365                    .with_context(|| {
366                        "Failed to obtain representation analysis result when handling custom Slater determinant source"
367                    })?
368                    .determinant_symmetry()
369                    .as_ref()
370                    .map_err(|err| format_err!(err.clone()))
371                    .with_context(|| {
372                        "Failed to obtain determinant symmetry from representation analysis result when handling custom Slater determinant source"
373                    })?
374                    .to_string();
375                Ok((group_name, sym))
376            }
377        }
378    }
379}
380
381/// Enumerated type indicating the order the matrix elements are traversed when stored into or
382/// read in from a binary file.
383#[derive(Clone, Serialize, Deserialize, Default)]
384pub enum MatrixOrder {
385    #[default]
386    RowMajor,
387    ColMajor,
388}
389
390/// Enumerated type indicating the byte order of numerical values in binary files.
391#[derive(Clone, Serialize, Deserialize, Default)]
392pub enum ByteOrder {
393    #[default]
394    LittleEndian,
395    BigEndian,
396}