qsym2/interfaces/binaries/
mod.rs

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