1use std::path::PathBuf;
4
5use itertools::Itertools;
6use ndarray::{Array1, Array2, ShapeBuilder};
7use num_complex::Complex;
8use numpy::{PyArrayMethods, ToPyArray};
9use pyo3::exceptions::{PyIOError, PyRuntimeError};
10use pyo3::{IntoPyObjectExt, prelude::*};
11
12use crate::analysis::Overlap;
13use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled};
14use crate::bindings::python::integrals::{PyBasisAngularOrder, PyStructureConstraint};
15use crate::bindings::python::projection::PyProjectionTarget;
16use crate::bindings::python::representation_analysis::PyArray2RC;
17use crate::bindings::python::representation_analysis::multideterminant::{
18 PyMultiDeterminantsComplex, PyMultiDeterminantsReal,
19};
20use crate::bindings::python::representation_analysis::slater_determinant::PySlaterDeterminant;
21use crate::drivers::QSym2Driver;
22use crate::drivers::projection::slater_determinant::{
23 SlaterDeterminantProjectionDriver, SlaterDeterminantProjectionParams,
24};
25use crate::drivers::representation_analysis::{
26 CharacterTableDisplay, MagneticSymmetryAnalysisKind,
27};
28use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
29use crate::io::format::qsym2_output;
30use crate::io::{QSym2FileType, read_qsym2_binary};
31use crate::symmetry::symmetry_group::UnitaryRepresentedSymmetryGroup;
32use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
33use crate::target::noci::basis::Basis;
34
35type C128 = Complex<f64>;
36
37#[pyfunction]
83#[pyo3(signature = (
84 inp_sym,
85 pydet,
86 projection_targets,
87 density_matrix_calculation_thresholds,
88 pybaos,
89 use_magnetic_group,
90 use_double_group,
91 symmetry_transformation_kind,
92 write_character_table=true,
93 infinite_order_to_finite=None,
94 sao=None,
95 sao_h=None,
96))]
97pub fn project_slater_determinant(
98 py: Python<'_>,
99 inp_sym: PathBuf,
100 pydet: PySlaterDeterminant,
101 projection_targets: Vec<PyProjectionTarget>,
102 density_matrix_calculation_thresholds: Option<(f64, f64)>,
103 pybaos: Vec<PyBasisAngularOrder>,
104 use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
105 use_double_group: bool,
106 symmetry_transformation_kind: SymmetryTransformationKind,
107 write_character_table: bool,
108 infinite_order_to_finite: Option<u32>,
109 sao: Option<PyArray2RC>,
110 sao_h: Option<PyArray2RC>,
111) -> PyResult<(Vec<String>, Py<PyAny>)> {
112 let pd_res: SymmetryGroupDetectionResult =
113 read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
114 .map_err(|err| PyIOError::new_err(err.to_string()))?;
115
116 let mut file_name = inp_sym.to_path_buf();
117 file_name.set_extension(QSym2FileType::Sym.ext());
118 qsym2_output!(
119 "Symmetry-group detection results read in from {}.",
120 file_name.display(),
121 );
122 qsym2_output!("");
123
124 let mol = &pd_res.pre_symmetry.recentred_molecule;
125 let baos = pybaos
126 .iter()
127 .map(|bao| {
128 bao.to_qsym2(mol)
129 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
130 })
131 .collect::<Result<Vec<_>, _>>()?;
132 let baos_ref = baos.iter().collect::<Vec<_>>();
133 let augment_to_generalised = match symmetry_transformation_kind {
134 SymmetryTransformationKind::SpatialWithSpinTimeReversal
135 | SymmetryTransformationKind::Spin
136 | SymmetryTransformationKind::SpinSpatial => true,
137 SymmetryTransformationKind::Spatial => false,
138 };
139
140 let symbolic_projection_targets = projection_targets
141 .iter()
142 .filter_map(|pt| match pt {
143 PyProjectionTarget::Symbolic(symbolic_pt) => Some(symbolic_pt.clone()),
144 PyProjectionTarget::Numeric(_) => None,
145 })
146 .collect_vec();
147 let numeric_projection_targets = projection_targets
148 .iter()
149 .filter_map(|pt| match pt {
150 PyProjectionTarget::Symbolic(_) => None,
151 PyProjectionTarget::Numeric(numeric_pt) => Some(*numeric_pt),
152 })
153 .collect_vec();
154
155 let sdp_params = SlaterDeterminantProjectionParams::builder()
156 .symbolic_projection_targets(Some(symbolic_projection_targets))
157 .numeric_projection_targets(Some(numeric_projection_targets))
158 .use_magnetic_group(use_magnetic_group.clone())
159 .use_double_group(use_double_group)
160 .symmetry_transformation_kind(symmetry_transformation_kind)
161 .write_character_table(if write_character_table {
162 Some(CharacterTableDisplay::Symbolic)
163 } else {
164 None
165 })
166 .infinite_order_to_finite(infinite_order_to_finite)
167 .build()
168 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
169
170 match pydet {
190 PySlaterDeterminant::Real(pydet_r) => {
191 if matches!(
192 pydet_r.structure_constraint,
193 PyStructureConstraint::SpinOrbitCoupled(_)
194 ) {
195 return Err(PyRuntimeError::new_err("Real determinants are not compatible with spin--orbit-coupled structure constraint.".to_string()));
196 }
197
198 let sao_opt = sao.and_then(|pysao| match pysao {
199 PyArray2RC::Real(pysao_r) => Some(pysao_r.to_owned_array()),
200 PyArray2RC::Complex(_) => None,
201 });
202 let sao_h_opt = sao_h.and_then(|pysao_h| match pysao_h {
203 PyArray2RC::Real(pysao_h_r) => Some(pysao_h_r.to_owned_array()),
204 PyArray2RC::Complex(_) => None,
205 });
206
207 let det_r = if augment_to_generalised {
208 pydet_r
209 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
210 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
211 .to_generalised()
212 } else {
213 pydet_r
214 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
215 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
216 };
217
218 let projected_sds = match &use_magnetic_group {
219 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
220 return Err(PyRuntimeError::new_err(
221 "Projection using corepresentations is not yet supported.",
222 ));
223 }
224 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
225 let mut sdp_driver = SlaterDeterminantProjectionDriver::<
226 UnitaryRepresentedSymmetryGroup,
227 f64,
228 _,
229 >::builder()
230 .parameters(&sdp_params)
231 .determinant(&det_r)
232 .symmetry_group(&pd_res)
233 .sao(sao_opt.as_ref())
234 .sao_h(sao_h_opt.as_ref())
235 .build()
236 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
237 py.detach(|| {
238 sdp_driver
239 .run()
240 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
241 })?;
242 sdp_driver
243 .result()
244 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
245 .projected_determinants()
246 .clone()
247 }
248 };
249 let (rows, (coefficientss, energies)): (Vec<_>, (Vec<_>, Vec<_>)) = projected_sds
250 .iter()
251 .map(|(row, multidet_res)| {
252 let (coefficients, energy) = multidet_res
253 .as_ref()
254 .and_then(|multidet| {
255 let coefficients =
256 multidet.coefficients().iter().cloned().collect_vec();
257 let energy = *multidet.energy().unwrap_or(&f64::NAN);
258 Ok((coefficients, energy))
259 })
260 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
261 Ok::<_, PyErr>((row.to_string(), (coefficients, energy)))
262 })
263 .collect::<Result<Vec<_>, _>>()?
264 .into_iter()
265 .unzip();
266 let basis = projected_sds
267 .iter()
268 .next()
269 .and_then(|(_, multidet_res)| {
270 multidet_res.as_ref().ok().and_then(|multidet| {
271 multidet
272 .basis()
273 .iter()
274 .map(|det_res| det_res.and_then(|det| det.to_python(py)))
275 .collect::<Result<Vec<_>, _>>()
276 .ok()
277 })
278 })
279 .ok_or_else(|| {
280 PyRuntimeError::new_err(
281 "Unable to obtain the basis of Slater determinants.".to_string(),
282 )
283 })?;
284 let coefficientss_arr = Array2::from_shape_vec(
285 (basis.len(), coefficientss.len()).f(),
286 coefficientss.into_iter().flatten().collect_vec(),
287 )
288 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
289 .to_pyarray(py);
290 let energies_arr = Array1::from_vec(energies).to_pyarray(py);
291 let density_matrices = match (density_matrix_calculation_thresholds, sao_opt.as_ref()) {
292 (Some((thresh_offdiag, thresh_zeroov)), Some(sao)) => Some(
293 projected_sds
294 .iter()
295 .map(|(_, multidet_res)| {
296 multidet_res
297 .as_ref()
298 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
299 .and_then(|multidet| {
300 multidet
301 .overlap(multidet, sao_opt.as_ref(), sao_h_opt.as_ref())
302 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
303 .and_then(|sq_norm| {
304 multidet
305 .density_matrix(
306 &sao.view(),
307 thresh_offdiag,
308 thresh_zeroov,
309 )
310 .map_err(|err| {
311 PyRuntimeError::new_err(err.to_string())
312 })
313 .map(|denmat| (denmat / sq_norm).to_pyarray(py))
314 })
315 })
316 })
317 .collect::<Result<Vec<_>, _>>()?,
318 ),
319 _ => None,
320 };
321 let pymultidet = PyMultiDeterminantsReal::new(
322 basis,
323 coefficientss_arr,
324 energies_arr,
325 density_matrices,
326 pydet_r.threshold,
327 )
328 .into_py_any(py)?;
329 Ok((rows, pymultidet))
330 }
331 PySlaterDeterminant::Complex(pydet_c) => {
332 let sao_opt = sao.and_then(|pysao| match pysao {
333 PyArray2RC::Real(pysao_r) => Some(pysao_r.to_owned_array().mapv(Complex::from)),
334 PyArray2RC::Complex(pysao_c) => Some(pysao_c.to_owned_array()),
335 });
336 let sao_h_opt = sao_h.and_then(|pysao_h| match pysao_h {
337 PyArray2RC::Real(pysao_h_r) => Some(pysao_h_r.to_owned_array().mapv(Complex::from)),
338 PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
339 });
340
341 match pydet_c.structure_constraint {
342 PyStructureConstraint::SpinConstraint(_) => {
343 let det_c = if augment_to_generalised {
344 pydet_c
345 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
346 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
347 .to_generalised()
348 } else {
349 pydet_c
350 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
351 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
352 };
353
354 let projected_sds = match &use_magnetic_group {
355 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
356 return Err(PyRuntimeError::new_err(
357 "Projection using corepresentations is not yet supported.",
358 ));
359 }
360 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
361 let mut sdp_driver = SlaterDeterminantProjectionDriver::<
362 UnitaryRepresentedSymmetryGroup,
363 C128,
364 _,
365 >::builder()
366 .parameters(&sdp_params)
367 .determinant(&det_c)
368 .symmetry_group(&pd_res)
369 .sao(sao_opt.as_ref())
370 .sao_h(sao_h_opt.as_ref())
371 .build()
372 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
373 py.detach(|| {
374 sdp_driver
375 .run()
376 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
377 })?;
378 sdp_driver
379 .result()
380 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
381 .projected_determinants()
382 .clone()
383 }
384 };
385 let (rows, (coefficientss, energies)): (Vec<_>, (Vec<_>, Vec<_>)) =
386 projected_sds
387 .iter()
388 .map(|(row, multidet_res)| {
389 let (coefficients, energy) = multidet_res
390 .as_ref()
391 .and_then(|multidet| {
392 let coefficients =
393 multidet.coefficients().iter().cloned().collect_vec();
394 let energy =
395 *multidet.energy().unwrap_or(&Complex::from(f64::NAN));
396 Ok((coefficients, energy))
397 })
398 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
399 Ok::<_, PyErr>((row.to_string(), (coefficients, energy)))
400 })
401 .collect::<Result<Vec<_>, _>>()?
402 .into_iter()
403 .unzip();
404 let basis = projected_sds
405 .iter()
406 .next()
407 .and_then(|(_, multidet_res)| {
408 multidet_res.as_ref().ok().and_then(|multidet| {
409 multidet
410 .basis()
411 .iter()
412 .map(|det_res| det_res.and_then(|det| det.to_python(py)))
413 .collect::<Result<Vec<_>, _>>()
414 .ok()
415 })
416 })
417 .ok_or_else(|| {
418 PyRuntimeError::new_err(
419 "Unable to obtain the basis of Slater determinants.".to_string(),
420 )
421 })?;
422 let coefficientss_arr = Array2::from_shape_vec(
423 (basis.len(), coefficientss.len()).f(),
424 coefficientss.into_iter().flatten().collect_vec(),
425 )
426 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
427 .to_pyarray(py);
428 let energies_arr = Array1::from_vec(energies).to_pyarray(py);
429 let density_matrices =
430 match (density_matrix_calculation_thresholds, sao_opt.as_ref()) {
431 (Some((thresh_offdiag, thresh_zeroov)), Some(sao)) => Some(
432 projected_sds
433 .iter()
434 .map(|(_, multidet_res)| {
435 multidet_res
436 .as_ref()
437 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
438 .and_then(|multidet| {
439 multidet
440 .overlap(
441 multidet,
442 sao_opt.as_ref(),
443 sao_h_opt.as_ref(),
444 )
445 .map_err(|err| {
446 PyRuntimeError::new_err(err.to_string())
447 })
448 .and_then(|sq_norm| {
449 multidet
450 .density_matrix(
451 &sao.view(),
452 thresh_offdiag,
453 thresh_zeroov,
454 )
455 .map_err(|err| {
456 PyRuntimeError::new_err(
457 err.to_string(),
458 )
459 })
460 .map(|denmat| {
461 (denmat / sq_norm).to_pyarray(py)
462 })
463 })
464 })
465 })
466 .collect::<Result<Vec<_>, _>>()?,
467 ),
468 _ => None,
469 };
470
471 let pymultidet = PyMultiDeterminantsComplex::new(
472 basis,
473 coefficientss_arr,
474 energies_arr,
475 density_matrices,
476 pydet_c.threshold,
477 )
478 .into_py_any(py)?;
479 Ok((rows, pymultidet))
480 }
481 PyStructureConstraint::SpinOrbitCoupled(_) => {
482 let det_c = pydet_c
483 .to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
484 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
485
486 let projected_sds = match &use_magnetic_group {
487 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
488 return Err(PyRuntimeError::new_err(
489 "Projection using corepresentations is not yet supported.",
490 ));
491 }
492 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
493 let mut sdp_driver = SlaterDeterminantProjectionDriver::<
494 UnitaryRepresentedSymmetryGroup,
495 C128,
496 _,
497 >::builder()
498 .parameters(&sdp_params)
499 .determinant(&det_c)
500 .symmetry_group(&pd_res)
501 .sao(sao_opt.as_ref())
502 .sao_h(sao_h_opt.as_ref())
503 .build()
504 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
505 py.detach(|| {
506 sdp_driver
507 .run()
508 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
509 })?;
510 sdp_driver
511 .result()
512 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
513 .projected_determinants()
514 .clone()
515 }
516 };
517 let (rows, (coefficientss, energies)): (Vec<_>, (Vec<_>, Vec<_>)) =
518 projected_sds
519 .iter()
520 .map(|(row, multidet_res)| {
521 let (coefficients, energy) = multidet_res
522 .as_ref()
523 .and_then(|multidet| {
524 let coefficients =
525 multidet.coefficients().iter().cloned().collect_vec();
526 let energy =
527 *multidet.energy().unwrap_or(&Complex::from(f64::NAN));
528 Ok((coefficients, energy))
529 })
530 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
531 Ok::<_, PyErr>((row.to_string(), (coefficients, energy)))
532 })
533 .collect::<Result<Vec<_>, _>>()?
534 .into_iter()
535 .unzip();
536 let basis = projected_sds
537 .iter()
538 .next()
539 .and_then(|(_, multidet_res)| {
540 multidet_res.as_ref().ok().and_then(|multidet| {
541 multidet
542 .basis()
543 .iter()
544 .map(|det_res| det_res.and_then(|det| det.to_python(py)))
545 .collect::<Result<Vec<_>, _>>()
546 .ok()
547 })
548 })
549 .ok_or_else(|| {
550 PyRuntimeError::new_err(
551 "Unable to obtain the basis of Slater determinants.".to_string(),
552 )
553 })?;
554 let coefficientss_arr = Array2::from_shape_vec(
555 (basis.len(), coefficientss.len()).f(),
556 coefficientss.into_iter().flatten().collect_vec(),
557 )
558 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
559 .to_pyarray(py);
560 let energies_arr = Array1::from_vec(energies).to_pyarray(py);
561 let density_matrices =
562 match (density_matrix_calculation_thresholds, sao_opt.as_ref()) {
563 (Some((thresh_offdiag, thresh_zeroov)), Some(sao)) => Some(
564 projected_sds
565 .iter()
566 .map(|(_, multidet_res)| {
567 multidet_res
568 .as_ref()
569 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
570 .and_then(|multidet| {
571 multidet
572 .overlap(
573 multidet,
574 sao_opt.as_ref(),
575 sao_h_opt.as_ref(),
576 )
577 .map_err(|err| {
578 PyRuntimeError::new_err(err.to_string())
579 })
580 .and_then(|sq_norm| {
581 multidet
582 .density_matrix(
583 &sao.view(),
584 thresh_offdiag,
585 thresh_zeroov,
586 )
587 .map_err(|err| {
588 PyRuntimeError::new_err(
589 err.to_string(),
590 )
591 })
592 .map(|denmat| {
593 (denmat / sq_norm).to_pyarray(py)
594 })
595 })
596 })
597 })
598 .collect::<Result<Vec<_>, _>>()?,
599 ),
600 _ => None,
601 };
602
603 let pymultidet = PyMultiDeterminantsComplex::new(
604 basis,
605 coefficientss_arr,
606 energies_arr,
607 density_matrices,
608 pydet_c.threshold,
609 )
610 .into_py_any(py)?;
611 Ok((rows, pymultidet))
612 }
613 }
614 }
615 }
616}