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