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    fn builder() -> BinariesSlaterDeterminantSourceBuilder {
78        BinariesSlaterDeterminantSourceBuilder::default()
79    }
80}
81
82impl Default for BinariesSlaterDeterminantSource {
83    fn default() -> Self {
84        BinariesSlaterDeterminantSource {
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        }
101    }
102}
103
104impl SlaterDeterminantSourceHandle for BinariesSlaterDeterminantSource {
105    type Outcome = (String, String);
106
107    fn sd_source_handle(
108        &self,
109        pd_params_inp: &SymmetryGroupDetectionInputKind,
110        afa_params: &AngularFunctionRepAnalysisParams,
111        sda_params: &SlaterDeterminantRepAnalysisParams<f64>,
112    ) -> Result<Self::Outcome, anyhow::Error> {
113        let pd_res = match pd_params_inp {
114            SymmetryGroupDetectionInputKind::Parameters(pd_params) => {
115                let mut pd_driver = SymmetryGroupDetectionDriver::builder()
116                    .parameters(pd_params)
117                    .xyz(Some(self.xyz.clone()))
118                    .build()
119                    .with_context(|| "Unable to construct a symmetry-group detection driver when handling custom Slater determinant source")?;
120                pd_driver.run().with_context(|| {
121                    "Unable to run the symmetry-group detection driver successfully when handling custom Slater determinant source"
122                })?;
123                pd_driver
124                    .result()
125                    .with_context(|| "Unable to retrieve the symmetry-group detection result when handling custom Slater determinant source")?
126                    .clone()
127            }
128            SymmetryGroupDetectionInputKind::FromFile(pd_res_file) => read_qsym2_binary(
129                pd_res_file,
130                QSym2FileType::Sym,
131            )
132            .with_context(|| {
133                format!(
134                    "Unable to read `{}.qsym2.sym` when handling custom Slater determinant source",
135                    pd_res_file.display()
136                )
137            })?,
138        };
139        let mol = &pd_res.pre_symmetry.recentred_molecule;
140        let baos = self.baos.iter().map(|bao| bao.to_basis_angular_order(mol)
141            .with_context(|| "Unable to digest the input basis angular order information when handling custom Slater determinant source")).collect::<Result<Vec<_>, _>>()?;
142        let nfuncs_tot = baos.iter().map(|bao| bao.n_funcs()).sum::<usize>();
143
144        let sao_v = match self.byte_order {
145            ByteOrder::LittleEndian => {
146                NumericReader::<_, LittleEndian, f64>::from_file(&self.sao)
147                    .with_context(|| {
148                        "Unable to read the specified two-centre SAO file when handling custom Slater determinant source"
149                    })?.collect::<Vec<_>>()
150            }
151            ByteOrder::BigEndian => {
152                NumericReader::<_, BigEndian, f64>::from_file(&self.sao)
153                    .with_context(|| {
154                        "Unable to read the specified two-centre SAO file when handling custom Slater determinant source"
155                    })?.collect::<Vec<_>>()
156            }
157        };
158        let sao = match self.matrix_order {
159            MatrixOrder::RowMajor => Array2::from_shape_vec((nfuncs_tot, nfuncs_tot), sao_v)
160                .with_context(|| {
161                    "Unable to construct an AO overlap matrix from the read-in row-major binary file when handling custom Slater determinant source"
162                })?,
163            MatrixOrder::ColMajor => Array2::from_shape_vec((nfuncs_tot, nfuncs_tot).f(), sao_v)
164                .with_context(|| {
165                    "Unable to construct an AO overlap matrix from the read-in column-major binary file when handling custom Slater determinant source"
166                })?,
167        };
168
169        let sao_4c = if let Some(sao_4c_path) = self.sao_4c.as_ref() {
170            let sao_4c_v = match self.byte_order {
171                ByteOrder::LittleEndian => {
172                    NumericReader::<_, LittleEndian, f64>::from_file(sao_4c_path)
173                        .with_context(|| {
174                            "Unable to read the specified four-centre SAO file when handling custom Slater determinant source"
175                        })?.collect::<Vec<_>>()
176                }
177                ByteOrder::BigEndian => {
178                    NumericReader::<_, BigEndian, f64>::from_file(sao_4c_path)
179                        .with_context(|| {
180                            "Unable to read the specified four-centre SAO file when handling custom Slater determinant source"
181                        })?.collect::<Vec<_>>()
182                }
183            };
184            let sao_4c = match self.matrix_order {
185                MatrixOrder::RowMajor => Array4::from_shape_vec((nfuncs_tot, nfuncs_tot, nfuncs_tot, nfuncs_tot), sao_4c_v)
186                    .with_context(|| {
187                        "Unable to construct a four-centre AO overlap matrix from the read-in row-major binary file when handling custom Slater determinant source"
188                    })?,
189                MatrixOrder::ColMajor => Array4::from_shape_vec((nfuncs_tot, nfuncs_tot, nfuncs_tot, nfuncs_tot).f(), sao_4c_v)
190                    .with_context(|| {
191                        "Unable to construct a four-centre AO overlap matrix from the read-in column-major binary file when handling custom Slater determinant source"
192                    })?,
193            };
194            Some(sao_4c)
195        } else {
196            None
197        };
198
199        let cs_v = match self.byte_order {
200            ByteOrder::LittleEndian => self
201                .coefficients
202                .iter()
203                .map(|c_path| {
204                    NumericReader::<_, LittleEndian, f64>::from_file(c_path)
205                        .map(|r| r.collect::<Vec<_>>())
206                })
207                .collect::<Result<Vec<_>, _>>()
208                .with_context(|| {
209                    "Unable to read the specified coefficient binary file(s) when handling custom Slater determinant source"
210                })?,
211            ByteOrder::BigEndian => self
212                .coefficients
213                .iter()
214                .map(|c_path| {
215                    NumericReader::<_, BigEndian, f64>::from_file(c_path)
216                        .map(|r| r.collect::<Vec<_>>())
217                })
218                .collect::<Result<Vec<_>, _>>()
219                .with_context(|| {
220                    "Unable to read the specified coefficient binary file(s) when handling custom Slater determinant source"
221                })?,
222        };
223        let cs = match self.matrix_order {
224            MatrixOrder::RowMajor => cs_v
225                .into_iter()
226                .map(|c_v| {
227                    let nmo = c_v.len().div_euclid(nfuncs_tot);
228                    Array2::from_shape_vec((nfuncs_tot, nmo), c_v)
229                })
230                .collect::<Result<Vec<_>, _>>()
231                .with_context(|| {
232                    "Unable to construct coefficient matrix (matrices) from the read-in row-major binary file(s) when handling custom Slater determinant source"
233                })?,
234
235            MatrixOrder::ColMajor => cs_v
236                .into_iter()
237                .map(|c_v| {
238                    let nmo = c_v.len().div_euclid(nfuncs_tot);
239                    Array2::from_shape_vec((nfuncs_tot, nmo).f(), c_v)
240                })
241                .collect::<Result<Vec<_>, _>>()
242                .with_context(|| {
243                    "Unable to construct coefficient matrix (matrices) from the read-in column-major binary file(s) when handling custom Slater determinant source"
244                })?,
245        };
246
247        let occs = match self.byte_order {
248            ByteOrder::LittleEndian => self
249                .occupations
250                .iter()
251                .map(|occ_path| {
252                    Ok::<_, anyhow::Error>(Array1::from_vec(
253                        NumericReader::<_, LittleEndian, f64>::from_file(occ_path)
254                            .map(|r| r.collect::<Vec<f64>>())?,
255                    ))
256                })
257                .collect::<Result<Vec<_>, _>>()
258                .with_context(|| {
259                    "Unable to read the specified occupation binary file(s) when handling custom Slater determinant source"
260                })?,
261            ByteOrder::BigEndian => self
262                .occupations
263                .iter()
264                .map(|occ_path| {
265                    Ok::<_, anyhow::Error>(Array1::from_vec(
266                        NumericReader::<_, BigEndian, f64>::from_file(occ_path)
267                            .map(|r| r.collect::<Vec<f64>>())?,
268                    ))
269                })
270                .collect::<Result<Vec<_>, _>>()
271                .with_context(|| {
272                    "Unable to read occupation binary file(s) when handling custom Slater determinant source"
273                })?,
274        };
275
276        let det = SlaterDeterminant::<f64, SpinConstraint>::builder()
277            .coefficients(&cs)
278            .occupations(&occs)
279            .baos(baos.iter().collect_vec())
280            .mol(mol)
281            .structure_constraint(self.spin_constraint.clone())
282            .complex_symmetric(false)
283            .threshold(sda_params.linear_independence_threshold)
284            .build()
285            .with_context(|| "Failed to construct a Slater determinant when handling custom Slater determinant source")?;
286
287        match &sda_params.use_magnetic_group {
288            Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
289                let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
290                    MagneticRepresentedSymmetryGroup,
291                    f64,
292                    SpinConstraint,
293                >::builder()
294                .parameters(sda_params)
295                .angular_function_parameters(afa_params)
296                .determinant(&det)
297                .sao(&sao)
298                .sao_spatial_4c(sao_4c.as_ref())
299                .symmetry_group(&pd_res)
300                .build()
301                .with_context(|| {
302                    "Failed to construct a Slater determinant corepresentation analysis driver when handling custom Slater determinant source"
303                })?;
304                sda_driver
305                    .run()
306                    .with_context(|| {
307                        "Failed to execute the Slater determinant corepresentation analysis driver successfully when handling custom Slater determinant source"
308                    })?;
309                let group_name = pd_res
310                    .magnetic_symmetry
311                    .as_ref()
312                    .and_then(|magsym| magsym.group_name.clone())
313                    .ok_or(format_err!("Magnetic group name not found when handling custom Slater determinant source."))?;
314                let sym = sda_driver
315                    .result()
316                    .with_context(|| {
317                        "Failed to obtain corepresentation analysis result when handling custom Slater determinant source"
318                    })?
319                    .determinant_symmetry()
320                    .as_ref()
321                    .map_err(|err| format_err!(err.clone()))
322                    .with_context(|| {
323                        "Failed to obtain determinant symmetry from corepresentation analysis result when handling custom Slater determinant source"
324                    })?
325                    .to_string();
326                Ok((group_name, sym))
327            }
328            Some(MagneticSymmetryAnalysisKind::Representation) | None => {
329                let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
330                    UnitaryRepresentedSymmetryGroup,
331                    f64,
332                    SpinConstraint,
333                >::builder()
334                .parameters(sda_params)
335                .angular_function_parameters(afa_params)
336                .determinant(&det)
337                .sao(&sao)
338                .sao_spatial_4c(sao_4c.as_ref())
339                .symmetry_group(&pd_res)
340                .build()
341                .with_context(|| {
342                    "Failed to construct a Slater determinant representation analysis driver when handling custom Slater determinant source"
343                })?;
344                sda_driver
345                    .run()
346                    .with_context(|| {
347                        "Failed to execute the Slater determinant representation analysis driver successfully when handling custom Slater determinant source"
348                    })?;
349                let group_name = if sda_params.use_magnetic_group.is_none() {
350                    pd_res
351                        .unitary_symmetry
352                        .group_name
353                        .as_ref()
354                        .ok_or(format_err!("Unitary group name not found when handling custom Slater determinant source."))?.clone()
355                } else {
356                    pd_res
357                        .magnetic_symmetry
358                        .as_ref()
359                        .and_then(|magsym| magsym.group_name.clone())
360                        .ok_or(format_err!("Magnetic group name not found when handling custom Slater determinant source."))?
361                };
362                let sym = sda_driver
363                    .result()
364                    .with_context(|| {
365                        "Failed to obtain representation analysis result when handling custom Slater determinant source"
366                    })?
367                    .determinant_symmetry()
368                    .as_ref()
369                    .map_err(|err| format_err!(err.clone()))
370                    .with_context(|| {
371                        "Failed to obtain determinant symmetry from representation analysis result when handling custom Slater determinant source"
372                    })?
373                    .to_string();
374                Ok((group_name, sym))
375            }
376        }
377    }
378}
379
380/// Enumerated type indicating the order the matrix elements are traversed when stored into or
381/// read in from a binary file.
382#[derive(Clone, Serialize, Deserialize)]
383pub enum MatrixOrder {
384    RowMajor,
385    ColMajor,
386}
387
388impl Default for MatrixOrder {
389    fn default() -> Self {
390        MatrixOrder::RowMajor
391    }
392}
393
394/// Enumerated type indicating the byte order of numerical values in binary files.
395#[derive(Clone, Serialize, Deserialize)]
396pub enum ByteOrder {
397    LittleEndian,
398    BigEndian,
399}
400
401impl Default for ByteOrder {
402    fn default() -> Self {
403        ByteOrder::LittleEndian
404    }
405}