1use std::collections::HashSet;
4use std::path::PathBuf;
5
6use anyhow::{bail, format_err, Context};
7use ndarray::Array1;
8use num_complex::Complex;
9use numpy::{PyArray1, PyArray2, PyArrayMethods};
10use pyo3::exceptions::{PyIOError, PyRuntimeError};
11use pyo3::prelude::*;
12use pyo3::types::PyFunction;
13
14use crate::analysis::EigenvalueComparisonMode;
15use crate::angmom::spinor_rotation_3d::SpinConstraint;
16use crate::bindings::python::integrals::PyBasisAngularOrder;
17use crate::bindings::python::representation_analysis::slater_determinant::{
18 PySlaterDeterminant, PySlaterDeterminantComplex, PySlaterDeterminantReal,
19};
20use crate::bindings::python::representation_analysis::{PyArray1RC, PyArray2RC};
21use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
22use crate::drivers::representation_analysis::multideterminant::{
23 MultiDeterminantRepAnalysisDriver, MultiDeterminantRepAnalysisParams,
24};
25use crate::drivers::representation_analysis::{
26 CharacterTableDisplay, MagneticSymmetryAnalysisKind,
27};
28use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
29use crate::drivers::QSym2Driver;
30use crate::io::format::qsym2_output;
31use crate::io::{read_qsym2_binary, QSym2FileType};
32use crate::symmetry::symmetry_group::{
33 MagneticRepresentedSymmetryGroup, SymmetryGroupProperties, UnitaryRepresentedSymmetryGroup,
34};
35use crate::symmetry::symmetry_transformation::{SymmetryTransformable, SymmetryTransformationKind};
36use crate::target::determinant::SlaterDeterminant;
37use crate::target::noci::basis::{Basis, EagerBasis, OrbitBasis};
38use crate::target::noci::multideterminant::MultiDeterminant;
39
40type C128 = Complex<f64>;
41
42#[pyfunction]
110#[pyo3(signature = (
111 inp_sym,
112 pyorigins,
113 py_noci_solver,
114 pybao,
115 integrality_threshold,
116 linear_independence_threshold,
117 use_magnetic_group,
118 use_double_group,
119 use_cayley_table,
120 symmetry_transformation_kind,
121 eigenvalue_comparison_mode,
122 sao,
123 sao_h=None,
124 write_overlap_eigenvalues=true,
125 write_character_table=true,
126 infinite_order_to_finite=None,
127 angular_function_integrality_threshold=1e-7,
128 angular_function_linear_independence_threshold=1e-7,
129 angular_function_max_angular_momentum=2
130))]
131pub fn rep_analyse_multideterminants_orbit_basis(
132 py: Python<'_>,
133 inp_sym: PathBuf,
134 pyorigins: Vec<PySlaterDeterminant>,
135 py_noci_solver: Py<PyFunction>,
136 pybao: &PyBasisAngularOrder,
137 integrality_threshold: f64,
138 linear_independence_threshold: f64,
139 use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
140 use_double_group: bool,
141 use_cayley_table: bool,
142 symmetry_transformation_kind: SymmetryTransformationKind,
143 eigenvalue_comparison_mode: EigenvalueComparisonMode,
144 sao: PyArray2RC,
145 sao_h: Option<PyArray2RC>,
146 write_overlap_eigenvalues: bool,
147 write_character_table: bool,
148 infinite_order_to_finite: Option<u32>,
149 angular_function_integrality_threshold: f64,
150 angular_function_linear_independence_threshold: f64,
151 angular_function_max_angular_momentum: u32,
152) -> PyResult<()> {
153 let pd_res: SymmetryGroupDetectionResult =
155 read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
156 .map_err(|err| PyIOError::new_err(err.to_string()))?;
157
158 let mut file_name = inp_sym.to_path_buf();
159 file_name.set_extension(QSym2FileType::Sym.ext());
160 qsym2_output!(
161 "Symmetry-group detection results read in from {}.",
162 file_name.display(),
163 );
164 qsym2_output!("");
165
166 let mol = &pd_res.pre_symmetry.recentred_molecule;
168 let bao = pybao
169 .to_qsym2(mol)
170 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
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 noci_solver_r = |multidets: &Vec<SlaterDeterminant<f64, SpinConstraint>>| {
203 Python::with_gil(|py_inner| {
204 let pymultidets = multidets
205 .iter()
206 .map(|det| {
207 let pysc = det.structure_constraint().clone().try_into()?;
208 Ok(PySlaterDeterminantReal::new(
209 pysc,
210 det.complex_symmetric(),
211 det.coefficients()
212 .iter()
213 .map(|arr| PyArray2::from_array(py_inner, arr))
214 .collect::<Vec<_>>(),
215 det.occupations()
216 .iter()
217 .map(|arr| PyArray1::from_array(py_inner, arr))
218 .collect::<Vec<_>>(),
219 det.threshold(),
220 det.mo_energies().map(|mo_energies| {
221 mo_energies
222 .iter()
223 .map(|arr| PyArray1::from_array(py_inner, arr))
224 .collect::<Vec<_>>()
225 }),
226 det.energy().ok().cloned(),
227 ))
228 })
229 .collect::<Result<Vec<_>, anyhow::Error>>()
230 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
231 py_noci_solver
232 .call1(py_inner, (pymultidets,))
233 .and_then(|res| res.extract::<(Vec<f64>, Vec<Vec<f64>>)>(py_inner))
234 })
235 };
236 let noci_solver_c = |multidets: &Vec<SlaterDeterminant<C128, SpinConstraint>>| {
237 Python::with_gil(|py_inner| {
238 let pymultidets = multidets
239 .iter()
240 .map(|det| {
241 let pysc = det.structure_constraint().clone().try_into()?;
242 Ok(PySlaterDeterminantComplex::new(
243 pysc,
244 det.complex_symmetric(),
245 det.coefficients()
246 .iter()
247 .map(|arr| PyArray2::from_array(py_inner, arr))
248 .collect::<Vec<_>>(),
249 det.occupations()
250 .iter()
251 .map(|arr| PyArray1::from_array(py_inner, arr))
252 .collect::<Vec<_>>(),
253 det.threshold(),
254 det.mo_energies().map(|mo_energies| {
255 mo_energies
256 .iter()
257 .map(|arr| PyArray1::from_array(py_inner, arr))
258 .collect::<Vec<_>>()
259 }),
260 det.energy().ok().cloned(),
261 ))
262 })
263 .collect::<Result<Vec<_>, anyhow::Error>>()
264 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
265 py_noci_solver
266 .call1(py_inner, (pymultidets,))
267 .and_then(|res| res.extract::<(Vec<C128>, Vec<Vec<C128>>)>(py_inner))
268 })
269 };
270
271 let all_real = pyorigins
272 .iter()
273 .all(|pyorigin| matches!(pyorigin, PySlaterDeterminant::Real(_)));
274
275 match (all_real, &sao) {
276 (true, PyArray2RC::Real(pysao_r)) => {
277 let sao = pysao_r.to_owned_array();
281 let origins_r = if augment_to_generalised {
282 pyorigins
283 .iter()
284 .map(|pydet| {
285 if let PySlaterDeterminant::Real(pydet_r) = pydet {
286 pydet_r
287 .to_qsym2(&bao, mol)
288 .map(|det_r| det_r.to_generalised())
289 } else {
290 bail!("Unexpected complex type for an origin Slater determinant.")
291 }
292 })
293 .collect::<Result<Vec<_>, _>>()
294 } else {
295 pyorigins
296 .iter()
297 .map(|pydet| {
298 if let PySlaterDeterminant::Real(pydet_r) = pydet {
299 pydet_r.to_qsym2(&bao, mol)
300 } else {
301 bail!("Unexpected complex type for an origin Slater determinant.")
302 }
303 })
304 .collect::<Result<Vec<_>, _>>()
305 }
306 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
307
308 match &use_magnetic_group {
309 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
310 let group = py
312 .allow_threads(|| {
313 let magsym = pd_res
314 .magnetic_symmetry
315 .as_ref()
316 .ok_or(format_err!("Magnetic group required for orbit construction, but no magnetic symmetry found."))?;
317 if use_double_group {
318 MagneticRepresentedSymmetryGroup::from_molecular_symmetry(
319 magsym,
320 infinite_order_to_finite,
321 )
322 .and_then(|grp| grp.to_double_group())
323 } else {
324 MagneticRepresentedSymmetryGroup::from_molecular_symmetry(
325 magsym,
326 infinite_order_to_finite,
327 )
328 }
329 })
330 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
331
332 let orbit_basis = match symmetry_transformation_kind {
334 SymmetryTransformationKind::Spatial => {
335 OrbitBasis::builder()
336 .group(&group)
337 .origins(origins_r)
338 .action(|op, det| {
339 det.sym_transform_spatial(op).with_context(|| {
340 format!("Unable to apply `{op}` spatially on the origin multi-determinantal wavefunction")
341 })
342 })
343 .build()
344 }
345 SymmetryTransformationKind::SpatialWithSpinTimeReversal => {
346 OrbitBasis::builder()
347 .group(&group)
348 .origins(origins_r)
349 .action(|op, det| {
350 det.sym_transform_spatial_with_spintimerev(op).with_context(|| {
351 format!("Unable to apply `{op}` spatially (with spin-including time reversal) on the origin multi-determinantal wavefunction")
352 })
353 })
354 .build()
355 }
356 SymmetryTransformationKind::Spin => {
357 OrbitBasis::builder()
358 .group(&group)
359 .origins(origins_r)
360 .action(|op, det| {
361 det.sym_transform_spin(op).with_context(|| {
362 format!("Unable to apply `{op}` spin-wise on the origin multi-determinantal wavefunction")
363 })
364 })
365 .build()
366 }
367 SymmetryTransformationKind::SpinSpatial => {
368 OrbitBasis::builder()
369 .group(&group)
370 .origins(origins_r)
371 .action(|op, det| {
372 det.sym_transform_spin_spatial(op).with_context(|| {
373 format!("Unable to apply `{op}` spin-spatially on the origin multi-determinantal wavefunction")
374 })
375 })
376 .build()
377 }
378 }.map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
379
380 let dets = orbit_basis
382 .iter()
383 .collect::<Result<Vec<_>, _>>()
384 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
385
386 let (noci_energies_vec, noci_coeffs_vec) = noci_solver_r(&dets)
387 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
388 if noci_energies_vec.len() != noci_coeffs_vec.len()
389 || noci_coeffs_vec
390 .iter()
391 .map(|coeffs| coeffs.len())
392 .collect::<HashSet<_>>()
393 .len()
394 != 1
395 {
396 return Err(PyRuntimeError::new_err(
397 "Inconsistent dimensions encountered in NOCI results.",
398 ));
399 }
400 let multidets = noci_energies_vec
401 .into_iter()
402 .zip(noci_coeffs_vec.into_iter())
403 .map(|(energy, coeffs)| {
404 MultiDeterminant::builder()
405 .basis(orbit_basis.clone())
406 .coefficients(Array1::from_vec(coeffs))
407 .threshold(1e-7)
408 .energy(Ok(energy))
409 .build()
410 })
411 .collect::<Result<Vec<_>, _>>()
412 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
413
414 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
415 MagneticRepresentedSymmetryGroup,
416 f64,
417 _,
418 SpinConstraint,
419 >::builder()
420 .parameters(&mda_params)
421 .angular_function_parameters(&afa_params)
422 .multidets(multidets.iter().collect::<Vec<_>>())
423 .sao(&sao)
424 .sao_h(None) .symmetry_group(&pd_res)
426 .build()
427 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
428 py.allow_threads(|| {
429 mda_driver
430 .run()
431 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
432 })?
433 }
434 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
435 let group = py
437 .allow_threads(|| {
438 let sym = if use_magnetic_group.is_some() {
439 pd_res
440 .magnetic_symmetry
441 .as_ref()
442 .ok_or(format_err!("Magnetic group required for orbit construction, but no magnetic symmetry found."))?
443 } else {
444 &pd_res.unitary_symmetry
445 };
446 if use_double_group {
447 UnitaryRepresentedSymmetryGroup::from_molecular_symmetry(
448 sym,
449 infinite_order_to_finite,
450 )
451 .and_then(|grp| grp.to_double_group())
452 } else {
453 UnitaryRepresentedSymmetryGroup::from_molecular_symmetry(
454 sym,
455 infinite_order_to_finite,
456 )
457 }
458 })
459 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
460
461 let orbit_basis = match symmetry_transformation_kind {
463 SymmetryTransformationKind::Spatial => {
464 OrbitBasis::builder()
465 .group(&group)
466 .origins(origins_r)
467 .action(|op, det| {
468 det.sym_transform_spatial(op).with_context(|| {
469 format!("Unable to apply `{op}` spatially on the origin multi-determinantal wavefunction")
470 })
471 })
472 .build()
473 }
474 SymmetryTransformationKind::SpatialWithSpinTimeReversal => {
475 OrbitBasis::builder()
476 .group(&group)
477 .origins(origins_r)
478 .action(|op, det| {
479 det.sym_transform_spatial_with_spintimerev(op).with_context(|| {
480 format!("Unable to apply `{op}` spatially (with spin-including time reversal) on the origin multi-determinantal wavefunction")
481 })
482 })
483 .build()
484 }
485 SymmetryTransformationKind::Spin => {
486 OrbitBasis::builder()
487 .group(&group)
488 .origins(origins_r)
489 .action(|op, det| {
490 det.sym_transform_spin(op).with_context(|| {
491 format!("Unable to apply `{op}` spin-wise on the origin multi-determinantal wavefunction")
492 })
493 })
494 .build()
495 }
496 SymmetryTransformationKind::SpinSpatial => {
497 OrbitBasis::builder()
498 .group(&group)
499 .origins(origins_r)
500 .action(|op, det| {
501 det.sym_transform_spin_spatial(op).with_context(|| {
502 format!("Unable to apply `{op}` spin-spatially on the origin multi-determinantal wavefunction")
503 })
504 })
505 .build()
506 }
507 }.map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
508
509 let dets = orbit_basis
511 .iter()
512 .collect::<Result<Vec<_>, _>>()
513 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
514
515 let (noci_energies_vec, noci_coeffs_vec) = noci_solver_r(&dets)
516 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
517 if noci_energies_vec.len() != noci_coeffs_vec.len()
518 || noci_coeffs_vec
519 .iter()
520 .map(|coeffs| coeffs.len())
521 .collect::<HashSet<_>>()
522 .len()
523 != 1
524 {
525 return Err(PyRuntimeError::new_err(
526 "Inconsistent dimensions encountered in NOCI results.",
527 ));
528 }
529 let multidets = noci_energies_vec
530 .into_iter()
531 .zip(noci_coeffs_vec.into_iter())
532 .map(|(energy, coeffs)| {
533 MultiDeterminant::builder()
534 .basis(orbit_basis.clone())
535 .coefficients(Array1::from_vec(coeffs))
536 .threshold(1e-7)
537 .energy(Ok(energy))
538 .build()
539 })
540 .collect::<Result<Vec<_>, _>>()
541 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
542
543 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
544 UnitaryRepresentedSymmetryGroup,
545 f64,
546 _,
547 SpinConstraint,
548 >::builder()
549 .parameters(&mda_params)
550 .angular_function_parameters(&afa_params)
551 .multidets(multidets.iter().collect::<Vec<_>>())
552 .sao(&sao)
553 .sao_h(None) .symmetry_group(&pd_res)
555 .build()
556 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
557 py.allow_threads(|| {
558 mda_driver
559 .run()
560 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
561 })?
562 }
563 };
564 }
565 (_, _) => {
566 let sao_c = match sao {
570 PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
571 PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
572 };
573 let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
574 PyArray2RC::Real(pysao_h_r) => Some(pysao_h_r.to_owned_array().mapv(Complex::from)),
576 PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
577 });
578 let origins_c = if augment_to_generalised {
579 pyorigins
580 .iter()
581 .map(|pydet| match pydet {
582 PySlaterDeterminant::Real(pydet_r) => {
583 pydet_r.to_qsym2::<SpinConstraint>(&bao, mol).map(|det_r| {
584 SlaterDeterminant::<C128, SpinConstraint>::from(det_r)
585 .to_generalised()
586 })
587 }
588 PySlaterDeterminant::Complex(pydet_c) => pydet_c
589 .to_qsym2::<SpinConstraint>(&bao, mol)
590 .map(|det_c| det_c.to_generalised()),
591 })
592 .collect::<Result<Vec<_>, _>>()
593 } else {
594 pyorigins
595 .iter()
596 .map(|pydet| match pydet {
597 PySlaterDeterminant::Real(pydet_r) => pydet_r
598 .to_qsym2::<SpinConstraint>(&bao, mol)
599 .map(|det_r| SlaterDeterminant::<C128, SpinConstraint>::from(det_r)),
600 PySlaterDeterminant::Complex(pydet_c) => {
601 pydet_c.to_qsym2::<SpinConstraint>(&bao, mol)
602 }
603 })
604 .collect::<Result<Vec<_>, _>>()
605 }
606 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
607
608 match &use_magnetic_group {
609 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
610 let group = py
612 .allow_threads(|| {
613 let magsym = pd_res
614 .magnetic_symmetry
615 .as_ref()
616 .ok_or(format_err!("Magnetic group required for orbit construction, but no magnetic symmetry found."))?;
617 if use_double_group {
618 MagneticRepresentedSymmetryGroup::from_molecular_symmetry(
619 magsym,
620 infinite_order_to_finite,
621 )
622 .and_then(|grp| grp.to_double_group())
623 } else {
624 MagneticRepresentedSymmetryGroup::from_molecular_symmetry(
625 magsym,
626 infinite_order_to_finite,
627 )
628 }
629 })
630 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
631
632 let orbit_basis = match symmetry_transformation_kind {
634 SymmetryTransformationKind::Spatial => {
635 OrbitBasis::builder()
636 .group(&group)
637 .origins(origins_c)
638 .action(|op, det| {
639 det.sym_transform_spatial(op).with_context(|| {
640 format!("Unable to apply `{op}` spatially on the origin multi-determinantal wavefunction")
641 })
642 })
643 .build()
644 }
645 SymmetryTransformationKind::SpatialWithSpinTimeReversal => {
646 OrbitBasis::builder()
647 .group(&group)
648 .origins(origins_c)
649 .action(|op, det| {
650 det.sym_transform_spatial_with_spintimerev(op).with_context(|| {
651 format!("Unable to apply `{op}` spatially (with spin-including time reversal) on the origin multi-determinantal wavefunction")
652 })
653 })
654 .build()
655 }
656 SymmetryTransformationKind::Spin => {
657 OrbitBasis::builder()
658 .group(&group)
659 .origins(origins_c)
660 .action(|op, det| {
661 det.sym_transform_spin(op).with_context(|| {
662 format!("Unable to apply `{op}` spin-wise on the origin multi-determinantal wavefunction")
663 })
664 })
665 .build()
666 }
667 SymmetryTransformationKind::SpinSpatial => {
668 OrbitBasis::builder()
669 .group(&group)
670 .origins(origins_c)
671 .action(|op, det| {
672 det.sym_transform_spin_spatial(op).with_context(|| {
673 format!("Unable to apply `{op}` spin-spatially on the origin multi-determinantal wavefunction")
674 })
675 })
676 .build()
677 }
678 }.map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
679
680 let dets = orbit_basis
682 .iter()
683 .collect::<Result<Vec<_>, _>>()
684 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
685
686 let (noci_energies_vec, noci_coeffs_vec) = noci_solver_c(&dets)
687 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
688 if noci_energies_vec.len() != noci_coeffs_vec.len()
689 || noci_coeffs_vec
690 .iter()
691 .map(|coeffs| coeffs.len())
692 .collect::<HashSet<_>>()
693 .len()
694 != 1
695 {
696 return Err(PyRuntimeError::new_err(
697 "Inconsistent dimensions encountered in NOCI results.",
698 ));
699 }
700 let multidets = noci_energies_vec
701 .into_iter()
702 .zip(noci_coeffs_vec.into_iter())
703 .map(|(energy, coeffs)| {
704 MultiDeterminant::builder()
705 .basis(orbit_basis.clone())
706 .coefficients(Array1::from_vec(coeffs))
707 .threshold(1e-7)
708 .energy(Ok(energy))
709 .build()
710 })
711 .collect::<Result<Vec<_>, _>>()
712 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
713
714 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
715 MagneticRepresentedSymmetryGroup,
716 C128,
717 _,
718 SpinConstraint,
719 >::builder()
720 .parameters(&mda_params)
721 .angular_function_parameters(&afa_params)
722 .multidets(multidets.iter().collect::<Vec<_>>())
723 .sao(&sao_c)
724 .sao_h(sao_h_c.as_ref())
725 .symmetry_group(&pd_res)
726 .build()
727 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
728 py.allow_threads(|| {
729 mda_driver
730 .run()
731 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
732 })?
733 }
734 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
735 let group = py
737 .allow_threads(|| {
738 let sym = if use_magnetic_group.is_some() {
739 pd_res
740 .magnetic_symmetry
741 .as_ref()
742 .ok_or(format_err!("Magnetic group required for orbit construction, but no magnetic symmetry found."))?
743 } else {
744 &pd_res.unitary_symmetry
745 };
746 if use_double_group {
747 UnitaryRepresentedSymmetryGroup::from_molecular_symmetry(
748 sym,
749 infinite_order_to_finite,
750 )
751 .and_then(|grp| grp.to_double_group())
752 } else {
753 UnitaryRepresentedSymmetryGroup::from_molecular_symmetry(
754 sym,
755 infinite_order_to_finite,
756 )
757 }
758 })
759 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
760
761 let orbit_basis = match symmetry_transformation_kind {
763 SymmetryTransformationKind::Spatial => {
764 OrbitBasis::builder()
765 .group(&group)
766 .origins(origins_c)
767 .action(|op, det| {
768 det.sym_transform_spatial(op).with_context(|| {
769 format!("Unable to apply `{op}` spatially on the origin multi-determinantal wavefunction")
770 })
771 })
772 .build()
773 }
774 SymmetryTransformationKind::SpatialWithSpinTimeReversal => {
775 OrbitBasis::builder()
776 .group(&group)
777 .origins(origins_c)
778 .action(|op, det| {
779 det.sym_transform_spatial_with_spintimerev(op).with_context(|| {
780 format!("Unable to apply `{op}` spatially (with spin-including time reversal) on the origin multi-determinantal wavefunction")
781 })
782 })
783 .build()
784 }
785 SymmetryTransformationKind::Spin => {
786 OrbitBasis::builder()
787 .group(&group)
788 .origins(origins_c)
789 .action(|op, det| {
790 det.sym_transform_spin(op).with_context(|| {
791 format!("Unable to apply `{op}` spin-wise on the origin multi-determinantal wavefunction")
792 })
793 })
794 .build()
795 }
796 SymmetryTransformationKind::SpinSpatial => {
797 OrbitBasis::builder()
798 .group(&group)
799 .origins(origins_c)
800 .action(|op, det| {
801 det.sym_transform_spin_spatial(op).with_context(|| {
802 format!("Unable to apply `{op}` spin-spatially on the origin multi-determinantal wavefunction")
803 })
804 })
805 .build()
806 }
807 }.map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
808
809 let dets = orbit_basis
811 .iter()
812 .collect::<Result<Vec<_>, _>>()
813 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
814
815 let (noci_energies_vec, noci_coeffs_vec) = noci_solver_c(&dets)
816 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
817 if noci_energies_vec.len() != noci_coeffs_vec.len()
818 || noci_coeffs_vec
819 .iter()
820 .map(|coeffs| coeffs.len())
821 .collect::<HashSet<_>>()
822 .len()
823 != 1
824 {
825 return Err(PyRuntimeError::new_err(
826 "Inconsistent dimensions encountered in NOCI results.",
827 ));
828 }
829 let multidets = noci_energies_vec
830 .into_iter()
831 .zip(noci_coeffs_vec.into_iter())
832 .map(|(energy, coeffs)| {
833 MultiDeterminant::builder()
834 .basis(orbit_basis.clone())
835 .coefficients(Array1::from_vec(coeffs))
836 .threshold(1e-7)
837 .energy(Ok(energy))
838 .build()
839 })
840 .collect::<Result<Vec<_>, _>>()
841 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
842
843 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
844 UnitaryRepresentedSymmetryGroup,
845 C128,
846 _,
847 SpinConstraint,
848 >::builder()
849 .parameters(&mda_params)
850 .angular_function_parameters(&afa_params)
851 .multidets(multidets.iter().collect::<Vec<_>>())
852 .sao(&sao_c)
853 .sao_h(sao_h_c.as_ref())
854 .symmetry_group(&pd_res)
855 .build()
856 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
857 py.allow_threads(|| {
858 mda_driver
859 .run()
860 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
861 })?
862 }
863 };
864 }
865 }
866 Ok(())
867}
868
869#[pyfunction]
935#[pyo3(signature = (
936 inp_sym,
937 pydets,
938 coefficients,
939 energies,
940 pybao,
941 integrality_threshold,
942 linear_independence_threshold,
943 use_magnetic_group,
944 use_double_group,
945 use_cayley_table,
946 symmetry_transformation_kind,
947 eigenvalue_comparison_mode,
948 sao,
949 sao_h=None,
950 write_overlap_eigenvalues=true,
951 write_character_table=true,
952 infinite_order_to_finite=None,
953 angular_function_integrality_threshold=1e-7,
954 angular_function_linear_independence_threshold=1e-7,
955 angular_function_max_angular_momentum=2
956))]
957pub fn rep_analyse_multideterminants_eager_basis(
958 py: Python<'_>,
959 inp_sym: PathBuf,
960 pydets: Vec<PySlaterDeterminant>,
961 coefficients: PyArray2RC,
962 energies: PyArray1RC,
963 pybao: &PyBasisAngularOrder,
964 integrality_threshold: f64,
965 linear_independence_threshold: f64,
966 use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
967 use_double_group: bool,
968 use_cayley_table: bool,
969 symmetry_transformation_kind: SymmetryTransformationKind,
970 eigenvalue_comparison_mode: EigenvalueComparisonMode,
971 sao: PyArray2RC,
972 sao_h: Option<PyArray2RC>,
973 write_overlap_eigenvalues: bool,
974 write_character_table: bool,
975 infinite_order_to_finite: Option<u32>,
976 angular_function_integrality_threshold: f64,
977 angular_function_linear_independence_threshold: f64,
978 angular_function_max_angular_momentum: u32,
979) -> PyResult<()> {
980 let pd_res: SymmetryGroupDetectionResult =
982 read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
983 .map_err(|err| PyIOError::new_err(err.to_string()))?;
984
985 let mut file_name = inp_sym.to_path_buf();
986 file_name.set_extension(QSym2FileType::Sym.ext());
987 qsym2_output!(
988 "Symmetry-group detection results read in from {}.",
989 file_name.display(),
990 );
991 qsym2_output!("");
992
993 let mol = &pd_res.pre_symmetry.recentred_molecule;
995 let bao = pybao
996 .to_qsym2(mol)
997 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
998 let augment_to_generalised = match symmetry_transformation_kind {
999 SymmetryTransformationKind::SpatialWithSpinTimeReversal
1000 | SymmetryTransformationKind::Spin
1001 | SymmetryTransformationKind::SpinSpatial => true,
1002 SymmetryTransformationKind::Spatial => false,
1003 };
1004 let afa_params = AngularFunctionRepAnalysisParams::builder()
1005 .integrality_threshold(angular_function_integrality_threshold)
1006 .linear_independence_threshold(angular_function_linear_independence_threshold)
1007 .max_angular_momentum(angular_function_max_angular_momentum)
1008 .build()
1009 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1010 let mda_params = MultiDeterminantRepAnalysisParams::<f64>::builder()
1011 .integrality_threshold(integrality_threshold)
1012 .linear_independence_threshold(linear_independence_threshold)
1013 .use_magnetic_group(use_magnetic_group.clone())
1014 .use_double_group(use_double_group)
1015 .use_cayley_table(use_cayley_table)
1016 .symmetry_transformation_kind(symmetry_transformation_kind.clone())
1017 .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
1018 .write_overlap_eigenvalues(write_overlap_eigenvalues)
1019 .write_character_table(if write_character_table {
1020 Some(CharacterTableDisplay::Symbolic)
1021 } else {
1022 None
1023 })
1024 .infinite_order_to_finite(infinite_order_to_finite)
1025 .build()
1026 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1027
1028 let all_real = pydets
1029 .iter()
1030 .all(|pydet| matches!(pydet, PySlaterDeterminant::Real(_)));
1031
1032 match (all_real, &coefficients, &energies, &sao) {
1033 (
1034 true,
1035 PyArray2RC::Real(pycoefficients_r),
1036 PyArray1RC::Real(pyenergies_r),
1037 PyArray2RC::Real(pysao_r),
1038 ) => {
1039 let sao = pysao_r.to_owned_array();
1043 let coefficients_r = pycoefficients_r.to_owned_array();
1044 let energies_r = pyenergies_r.to_owned_array();
1045 let dets_r = if augment_to_generalised {
1046 pydets
1047 .iter()
1048 .map(|pydet| {
1049 if let PySlaterDeterminant::Real(pydet_r) = pydet {
1050 pydet_r
1051 .to_qsym2(&bao, mol)
1052 .map(|det_r| det_r.to_generalised())
1053 } else {
1054 bail!("Unexpected complex type for a Slater determinant.")
1055 }
1056 })
1057 .collect::<Result<Vec<_>, _>>()
1058 } else {
1059 pydets
1060 .iter()
1061 .map(|pydet| {
1062 if let PySlaterDeterminant::Real(pydet_r) = pydet {
1063 pydet_r.to_qsym2(&bao, mol)
1064 } else {
1065 bail!("Unexpected complex type for a Slater determinant.")
1066 }
1067 })
1068 .collect::<Result<Vec<_>, _>>()
1069 }
1070 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1071
1072 let n_energies = energies_r.len();
1073 if coefficients_r.shape()[1] != n_energies {
1074 return Err(PyRuntimeError::new_err(
1075 "Mismatched number of NOCI energies and number of NOCI states.",
1076 ));
1077 }
1078
1079 let eager_basis = EagerBasis::builder()
1081 .elements(dets_r)
1082 .build()
1083 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1084
1085 let multidets = energies_r
1087 .iter()
1088 .zip(coefficients_r.columns())
1089 .map(|(energy, coeffs)| {
1090 MultiDeterminant::builder()
1091 .basis(eager_basis.clone())
1092 .coefficients(coeffs.to_owned())
1093 .threshold(1e-7)
1094 .energy(Ok(*energy))
1095 .build()
1096 })
1097 .collect::<Result<Vec<_>, _>>()
1098 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1099
1100 match &use_magnetic_group {
1102 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1103 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
1104 MagneticRepresentedSymmetryGroup,
1105 f64,
1106 _,
1107 SpinConstraint,
1108 >::builder()
1109 .parameters(&mda_params)
1110 .angular_function_parameters(&afa_params)
1111 .multidets(multidets.iter().collect::<Vec<_>>())
1112 .sao(&sao)
1113 .sao_h(None) .symmetry_group(&pd_res)
1115 .build()
1116 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1117
1118 py.allow_threads(|| {
1120 mda_driver
1121 .run()
1122 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1123 })?
1124 }
1125 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1126 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
1127 UnitaryRepresentedSymmetryGroup,
1128 f64,
1129 _,
1130 SpinConstraint,
1131 >::builder()
1132 .parameters(&mda_params)
1133 .angular_function_parameters(&afa_params)
1134 .multidets(multidets.iter().collect::<Vec<_>>())
1135 .sao(&sao)
1136 .sao_h(None) .symmetry_group(&pd_res)
1138 .build()
1139 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1140
1141 py.allow_threads(|| {
1143 mda_driver
1144 .run()
1145 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1146 })?
1147 }
1148 }
1149 }
1150 (_, _, _, _) => {
1151 let sao_c = match sao {
1155 PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
1156 PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
1157 };
1158 let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
1159 PyArray2RC::Real(pysao_h_r) => Some(pysao_h_r.to_owned_array().mapv(Complex::from)),
1161 PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
1162 });
1163 let coefficients_c = match coefficients {
1164 PyArray2RC::Real(pycoefficients_r) => {
1165 pycoefficients_r.to_owned_array().mapv(Complex::from)
1166 }
1167 PyArray2RC::Complex(pycoefficients_c) => pycoefficients_c.to_owned_array(),
1168 };
1169 let energies_c = match energies {
1170 PyArray1RC::Real(pyenergies_r) => pyenergies_r.to_owned_array().mapv(Complex::from),
1171 PyArray1RC::Complex(pyenergies_c) => pyenergies_c.to_owned_array(),
1172 };
1173 let dets_c = if augment_to_generalised {
1174 pydets
1175 .iter()
1176 .map(|pydet| match pydet {
1177 PySlaterDeterminant::Real(pydet_r) => {
1178 pydet_r.to_qsym2::<SpinConstraint>(&bao, mol).map(|det_r| {
1179 SlaterDeterminant::<C128, SpinConstraint>::from(det_r)
1180 .to_generalised()
1181 })
1182 }
1183 PySlaterDeterminant::Complex(pydet_c) => pydet_c
1184 .to_qsym2::<SpinConstraint>(&bao, mol)
1185 .map(|det_c| det_c.to_generalised()),
1186 })
1187 .collect::<Result<Vec<_>, _>>()
1188 } else {
1189 pydets
1190 .iter()
1191 .map(|pydet| match pydet {
1192 PySlaterDeterminant::Real(pydet_r) => pydet_r
1193 .to_qsym2::<SpinConstraint>(&bao, mol)
1194 .map(|det_r| SlaterDeterminant::<C128, SpinConstraint>::from(det_r)),
1195 PySlaterDeterminant::Complex(pydet_c) => {
1196 pydet_c.to_qsym2::<SpinConstraint>(&bao, mol)
1197 }
1198 })
1199 .collect::<Result<Vec<_>, _>>()
1200 }
1201 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1202
1203 let n_energies = energies_c.len();
1204 if coefficients_c.shape()[1] != n_energies {
1205 return Err(PyRuntimeError::new_err(
1206 "Mismatched number of NOCI energies and number of NOCI states.",
1207 ));
1208 }
1209
1210 let eager_basis = EagerBasis::builder()
1212 .elements(dets_c)
1213 .build()
1214 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1215
1216 let multidets = energies_c
1218 .iter()
1219 .zip(coefficients_c.columns())
1220 .map(|(energy, coeffs)| {
1221 MultiDeterminant::builder()
1222 .basis(eager_basis.clone())
1223 .coefficients(coeffs.to_owned())
1224 .threshold(1e-7)
1225 .energy(Ok(*energy))
1226 .build()
1227 })
1228 .collect::<Result<Vec<_>, _>>()
1229 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1230
1231 match &use_magnetic_group {
1233 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1234 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
1235 MagneticRepresentedSymmetryGroup,
1236 C128,
1237 _,
1238 SpinConstraint,
1239 >::builder()
1240 .parameters(&mda_params)
1241 .angular_function_parameters(&afa_params)
1242 .multidets(multidets.iter().collect::<Vec<_>>())
1243 .sao(&sao_c)
1244 .sao_h(sao_h_c.as_ref())
1245 .symmetry_group(&pd_res)
1246 .build()
1247 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1248
1249 py.allow_threads(|| {
1251 mda_driver
1252 .run()
1253 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1254 })?
1255 }
1256 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1257 let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
1258 UnitaryRepresentedSymmetryGroup,
1259 C128,
1260 _,
1261 SpinConstraint,
1262 >::builder()
1263 .parameters(&mda_params)
1264 .angular_function_parameters(&afa_params)
1265 .multidets(multidets.iter().collect::<Vec<_>>())
1266 .sao(&sao_c)
1267 .sao_h(sao_h_c.as_ref())
1268 .symmetry_group(&pd_res)
1269 .build()
1270 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1271
1272 py.allow_threads(|| {
1274 mda_driver
1275 .run()
1276 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1277 })?
1278 }
1279 }
1280 }
1281 }
1282 Ok(())
1283}