qsym2/bindings/python/representation_analysis/multideterminant/
multideterminant_eager_basis.rs1use std::collections::HashSet;
4use std::path::PathBuf;
5
6use anyhow::bail;
7use num_complex::Complex;
8use numpy::PyArrayMethods;
9use pyo3::exceptions::{PyIOError, PyRuntimeError};
10use pyo3::prelude::*;
11
12use crate::analysis::EigenvalueComparisonMode;
13use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled};
14use crate::bindings::python::integrals::{
15 PyBasisAngularOrder, PyStructureConstraint,
16};
17use crate::bindings::python::representation_analysis::slater_determinant::PySlaterDeterminant;
18use crate::bindings::python::representation_analysis::{PyArray1RC, PyArray2RC};
19use crate::drivers::QSym2Driver;
20use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
21use crate::drivers::representation_analysis::multideterminant::{
22 MultiDeterminantRepAnalysisDriver, MultiDeterminantRepAnalysisParams,
23};
24use crate::drivers::representation_analysis::{
25 CharacterTableDisplay, MagneticSymmetryAnalysisKind,
26};
27use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
28use crate::io::format::qsym2_output;
29use crate::io::{QSym2FileType, read_qsym2_binary};
30use crate::symmetry::symmetry_group::{
31 MagneticRepresentedSymmetryGroup, UnitaryRepresentedSymmetryGroup,
32};
33use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
34use crate::target::determinant::SlaterDeterminant;
35use crate::target::noci::basis::EagerBasis;
36use crate::target::noci::multideterminant::MultiDeterminant;
37
38type C128 = Complex<f64>;
39
40#[pyfunction]
101#[pyo3(signature = (
102 inp_sym,
103 pydets,
104 coefficients,
105 energies,
106 pybaos,
107 integrality_threshold,
108 linear_independence_threshold,
109 use_magnetic_group,
110 use_double_group,
111 use_cayley_table,
112 symmetry_transformation_kind,
113 eigenvalue_comparison_mode,
114 sao,
115 sao_h=None,
116 write_overlap_eigenvalues=true,
117 write_character_table=true,
118 infinite_order_to_finite=None,
119 angular_function_integrality_threshold=1e-7,
120 angular_function_linear_independence_threshold=1e-7,
121 angular_function_max_angular_momentum=2
122))]
123pub fn rep_analyse_multideterminants_eager_basis(
124 py: Python<'_>,
125 inp_sym: PathBuf,
126 pydets: Vec<PySlaterDeterminant>,
127 coefficients: PyArray2RC,
128 energies: PyArray1RC,
129 pybaos: Vec<PyBasisAngularOrder>,
130 integrality_threshold: f64,
131 linear_independence_threshold: f64,
132 use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
133 use_double_group: bool,
134 use_cayley_table: bool,
135 symmetry_transformation_kind: SymmetryTransformationKind,
136 eigenvalue_comparison_mode: EigenvalueComparisonMode,
137 sao: PyArray2RC,
138 sao_h: Option<PyArray2RC>,
139 write_overlap_eigenvalues: bool,
140 write_character_table: bool,
141 infinite_order_to_finite: Option<u32>,
142 angular_function_integrality_threshold: f64,
143 angular_function_linear_independence_threshold: f64,
144 angular_function_max_angular_momentum: u32,
145) -> PyResult<()> {
146 let pd_res: SymmetryGroupDetectionResult =
148 read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
149 .map_err(|err| PyIOError::new_err(err.to_string()))?;
150
151 let mut file_name = inp_sym.to_path_buf();
152 file_name.set_extension(QSym2FileType::Sym.ext());
153 qsym2_output!(
154 "Symmetry-group detection results read in from {}.",
155 file_name.display(),
156 );
157 qsym2_output!("");
158
159 let mol = &pd_res.pre_symmetry.recentred_molecule;
161
162 let baos = pybaos
163 .iter()
164 .map(|bao| {
165 bao.to_qsym2(mol)
166 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
167 })
168 .collect::<Result<Vec<_>, _>>()?;
169 let baos_ref = baos.iter().collect::<Vec<_>>();
170 let augment_to_generalised = match symmetry_transformation_kind {
171 SymmetryTransformationKind::SpatialWithSpinTimeReversal
172 | SymmetryTransformationKind::Spin
173 | SymmetryTransformationKind::SpinSpatial => true,
174 SymmetryTransformationKind::Spatial => false,
175 };
176 let afa_params = AngularFunctionRepAnalysisParams::builder()
177 .integrality_threshold(angular_function_integrality_threshold)
178 .linear_independence_threshold(angular_function_linear_independence_threshold)
179 .max_angular_momentum(angular_function_max_angular_momentum)
180 .build()
181 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
182 let mda_params = MultiDeterminantRepAnalysisParams::<f64>::builder()
183 .integrality_threshold(integrality_threshold)
184 .linear_independence_threshold(linear_independence_threshold)
185 .use_magnetic_group(use_magnetic_group.clone())
186 .use_double_group(use_double_group)
187 .use_cayley_table(use_cayley_table)
188 .symmetry_transformation_kind(symmetry_transformation_kind.clone())
189 .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
190 .write_overlap_eigenvalues(write_overlap_eigenvalues)
191 .write_character_table(if write_character_table {
192 Some(CharacterTableDisplay::Symbolic)
193 } else {
194 None
195 })
196 .infinite_order_to_finite(infinite_order_to_finite)
197 .build()
198 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
199
200 let all_real = pydets
201 .iter()
202 .all(|pydet| matches!(pydet, PySlaterDeterminant::Real(_)));
203
204 let structure_constraints_set = pydets
205 .iter()
206 .map(|pydet| match pydet {
207 PySlaterDeterminant::Real(pydet) => pydet.structure_constraint().clone(),
208 PySlaterDeterminant::Complex(pydet) => pydet.structure_constraint().clone(),
209 })
210 .collect::<HashSet<_>>();
211 if structure_constraints_set.len() != 1 {
212 return Err(PyRuntimeError::new_err(
213 "Inconsistent structure constraints across origin determinants.`",
214 ));
215 };
216 let structure_constraint = structure_constraints_set
217 .iter()
218 .next()
219 .ok_or_else(|| PyRuntimeError::new_err("Unable to retrieve the structure constraint."))?;
220
221 match (all_real, &coefficients, &energies, &sao) {
241 (
242 true,
243 PyArray2RC::Real(pycoefficients_r),
244 PyArray1RC::Real(pyenergies_r),
245 PyArray2RC::Real(pysao_r),
246 ) => {
247 if matches!(
250 structure_constraint,
251 PyStructureConstraint::SpinOrbitCoupled(_)
252 ) {
253 return Err(PyRuntimeError::new_err(
254 "Real determinants cannot support spin--orbit-coupled structure constraint.",
255 ));
256 }
257
258 let sao_r = pysao_r.to_owned_array();
260 let coefficients_r = pycoefficients_r.to_owned_array();
261 let energies_r = pyenergies_r.to_owned_array();
262 let dets_r = if augment_to_generalised {
263 pydets
264 .iter()
265 .map(|pydet| {
266 if let PySlaterDeterminant::Real(pydet_r) = pydet {
267 pydet_r
268 .to_qsym2(&baos_ref, mol)
269 .map(|det_r| det_r.to_generalised())
270 } else {
271 bail!("Unexpected complex type for a Slater determinant.")
272 }
273 })
274 .collect::<Result<Vec<_>, _>>()
275 } else {
276 pydets
277 .iter()
278 .map(|pydet| {
279 if let PySlaterDeterminant::Real(pydet_r) = pydet {
280 pydet_r.to_qsym2(&baos_ref, mol)
281 } else {
282 bail!("Unexpected complex type for a Slater determinant.")
283 }
284 })
285 .collect::<Result<Vec<_>, _>>()
286 }
287 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
288
289 let n_energies = energies_r.len();
290 if coefficients_r.shape()[1] != n_energies {
291 return Err(PyRuntimeError::new_err(
292 "Mismatched number of NOCI energies and number of NOCI states.",
293 ));
294 }
295
296 let eager_basis = EagerBasis::builder()
298 .elements(dets_r)
299 .build()
300 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
301
302 let multidets = energies_r
304 .iter()
305 .zip(coefficients_r.columns())
306 .map(|(energy, coeffs)| {
307 MultiDeterminant::builder()
308 .basis(eager_basis.clone())
309 .coefficients(coeffs.to_owned())
310 .threshold(1e-7)
311 .energy(Ok(*energy))
312 .build()
313 })
314 .collect::<Result<Vec<_>, _>>()
315 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
316
317 match &use_magnetic_group {
319 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
320 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
321 MagneticRepresentedSymmetryGroup,
322 f64,
323 _,
324 SpinConstraint,
325 >::builder()
326 .parameters(&mda_params)
327 .angular_function_parameters(&afa_params)
328 .multidets(multidets.iter().collect::<Vec<_>>())
329 .sao(&sao_r)
330 .sao_h(None) .symmetry_group(&pd_res)
332 .build()
333 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
334
335 py.detach(|| {
337 mda_driver
338 .run()
339 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
340 })?
341 }
342 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
343 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
344 UnitaryRepresentedSymmetryGroup,
345 f64,
346 _,
347 SpinConstraint,
348 >::builder()
349 .parameters(&mda_params)
350 .angular_function_parameters(&afa_params)
351 .multidets(multidets.iter().collect::<Vec<_>>())
352 .sao(&sao_r)
353 .sao_h(None) .symmetry_group(&pd_res)
355 .build()
356 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
357
358 py.detach(|| {
360 mda_driver
361 .run()
362 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
363 })?
364 }
365 }
366 }
367 (_, _, _, _) => {
368 let sao_c = match sao {
372 PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
373 PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
374 };
375 let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
376 PyArray2RC::Real(pysao_h_r) => Some(pysao_h_r.to_owned_array().mapv(Complex::from)),
378 PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
379 });
380 let coefficients_c = match coefficients {
381 PyArray2RC::Real(pycoefficients_r) => {
382 pycoefficients_r.to_owned_array().mapv(Complex::from)
383 }
384 PyArray2RC::Complex(pycoefficients_c) => pycoefficients_c.to_owned_array(),
385 };
386 let energies_c = match energies {
387 PyArray1RC::Real(pyenergies_r) => pyenergies_r.to_owned_array().mapv(Complex::from),
388 PyArray1RC::Complex(pyenergies_c) => pyenergies_c.to_owned_array(),
389 };
390
391 match structure_constraint {
392 PyStructureConstraint::SpinConstraint(_) => {
393 let dets_c = if augment_to_generalised {
394 pydets
395 .iter()
396 .map(|pydet| match pydet {
397 PySlaterDeterminant::Real(pydet_r) => pydet_r
398 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
399 .map(|det_r| {
400 SlaterDeterminant::<C128, SpinConstraint>::from(det_r)
401 .to_generalised()
402 }),
403 PySlaterDeterminant::Complex(pydet_c) => pydet_c
404 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
405 .map(|det_c| det_c.to_generalised()),
406 })
407 .collect::<Result<Vec<_>, _>>()
408 } else {
409 pydets
410 .iter()
411 .map(|pydet| match pydet {
412 PySlaterDeterminant::Real(pydet_r) => pydet_r
413 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
414 .map(|det_r| {
415 SlaterDeterminant::<C128, SpinConstraint>::from(det_r)
416 }),
417 PySlaterDeterminant::Complex(pydet_c) => {
418 pydet_c.to_qsym2::<SpinConstraint>(&baos_ref, mol)
419 }
420 })
421 .collect::<Result<Vec<_>, _>>()
422 }
423 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
424
425 let n_energies = energies_c.len();
426 if coefficients_c.shape()[1] != n_energies {
427 return Err(PyRuntimeError::new_err(
428 "Mismatched number of NOCI energies and number of NOCI states.",
429 ));
430 }
431
432 let eager_basis = EagerBasis::builder()
434 .elements(dets_c)
435 .build()
436 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
437
438 let multidets = energies_c
440 .iter()
441 .zip(coefficients_c.columns())
442 .map(|(energy, coeffs)| {
443 MultiDeterminant::builder()
444 .basis(eager_basis.clone())
445 .coefficients(coeffs.to_owned())
446 .threshold(1e-7)
447 .energy(Ok(*energy))
448 .build()
449 })
450 .collect::<Result<Vec<_>, _>>()
451 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
452
453 match &use_magnetic_group {
455 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
456 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
457 MagneticRepresentedSymmetryGroup,
458 C128,
459 _,
460 SpinConstraint,
461 >::builder()
462 .parameters(&mda_params)
463 .angular_function_parameters(&afa_params)
464 .multidets(multidets.iter().collect::<Vec<_>>())
465 .sao(&sao_c)
466 .sao_h(sao_h_c.as_ref())
467 .symmetry_group(&pd_res)
468 .build()
469 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
470
471 py.detach(|| {
473 mda_driver
474 .run()
475 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
476 })?
477 }
478 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
479 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
480 UnitaryRepresentedSymmetryGroup,
481 C128,
482 _,
483 SpinConstraint,
484 >::builder()
485 .parameters(&mda_params)
486 .angular_function_parameters(&afa_params)
487 .multidets(multidets.iter().collect::<Vec<_>>())
488 .sao(&sao_c)
489 .sao_h(sao_h_c.as_ref())
490 .symmetry_group(&pd_res)
491 .build()
492 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
493
494 py.detach(|| {
496 mda_driver
497 .run()
498 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
499 })?
500 }
501 }
502 }
503 PyStructureConstraint::SpinOrbitCoupled(_) => {
504 let dets_c = pydets
505 .iter()
506 .map(|pydet| match pydet {
507 PySlaterDeterminant::Real(pydet_r) => pydet_r
508 .to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
509 .map(|det_r| {
510 SlaterDeterminant::<C128, SpinOrbitCoupled>::from(det_r)
511 }),
512 PySlaterDeterminant::Complex(pydet_c) => {
513 pydet_c.to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
514 }
515 })
516 .collect::<Result<Vec<_>, _>>()
517 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
518
519 let n_energies = energies_c.len();
520 if coefficients_c.shape()[1] != n_energies {
521 return Err(PyRuntimeError::new_err(
522 "Mismatched number of NOCI energies and number of NOCI states.",
523 ));
524 }
525
526 let eager_basis = EagerBasis::builder()
528 .elements(dets_c)
529 .build()
530 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
531
532 let multidets = energies_c
534 .iter()
535 .zip(coefficients_c.columns())
536 .map(|(energy, coeffs)| {
537 MultiDeterminant::builder()
538 .basis(eager_basis.clone())
539 .coefficients(coeffs.to_owned())
540 .threshold(1e-7)
541 .energy(Ok(*energy))
542 .build()
543 })
544 .collect::<Result<Vec<_>, _>>()
545 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
546
547 match &use_magnetic_group {
549 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
550 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
551 MagneticRepresentedSymmetryGroup,
552 C128,
553 _,
554 SpinOrbitCoupled,
555 >::builder()
556 .parameters(&mda_params)
557 .angular_function_parameters(&afa_params)
558 .multidets(multidets.iter().collect::<Vec<_>>())
559 .sao(&sao_c)
560 .sao_h(sao_h_c.as_ref())
561 .symmetry_group(&pd_res)
562 .build()
563 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
564
565 py.detach(|| {
567 mda_driver
568 .run()
569 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
570 })?
571 }
572 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
573 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
574 UnitaryRepresentedSymmetryGroup,
575 C128,
576 _,
577 SpinOrbitCoupled,
578 >::builder()
579 .parameters(&mda_params)
580 .angular_function_parameters(&afa_params)
581 .multidets(multidets.iter().collect::<Vec<_>>())
582 .sao(&sao_c)
583 .sao_h(sao_h_c.as_ref())
584 .symmetry_group(&pd_res)
585 .build()
586 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
587
588 py.detach(|| {
590 mda_driver
591 .run()
592 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
593 })?
594 }
595 }
596 }
597 }
598 }
599 }
600 Ok(())
601}