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