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::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled};
13use crate::bindings::python::integrals::{PyBasisAngularOrder, PyStructureConstraint};
14use crate::bindings::python::projection::PyProjectionTarget;
15use crate::bindings::python::representation_analysis::PyArray2RC;
16use crate::bindings::python::representation_analysis::multideterminant::{
17 PyMultiDeterminantsComplex, PyMultiDeterminantsReal,
18};
19use crate::bindings::python::representation_analysis::slater_determinant::PySlaterDeterminant;
20use crate::drivers::QSym2Driver;
21use crate::drivers::projection::slater_determinant::{
22 SlaterDeterminantProjectionDriver, SlaterDeterminantProjectionParams,
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::UnitaryRepresentedSymmetryGroup;
31use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
32use crate::target::noci::basis::Basis;
33
34type C128 = Complex<f64>;
35
36#[allow(clippy::too_many_arguments)]
82#[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 .map(|multidet| {
255 let coefficients =
256 multidet.coefficients().iter().cloned().collect_vec();
257 let energy = *multidet.energy().unwrap_or(&f64::NAN);
258 (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 .density_matrix(
302 &sao.view(),
303 thresh_offdiag,
304 thresh_zeroov,
305 true,
306 )
307 .map(|denmat| denmat.to_pyarray(py))
308 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
309 })
310 })
311 .collect::<Result<Vec<_>, _>>()?,
312 ),
313 _ => None,
314 };
315 let pymultidet = PyMultiDeterminantsReal::new(
316 basis,
317 coefficientss_arr,
318 energies_arr,
319 density_matrices,
320 pydet_r.threshold,
321 )
322 .into_py_any(py)?;
323 Ok((rows, pymultidet))
324 }
325 PySlaterDeterminant::Complex(pydet_c) => {
326 let sao_opt = sao.map(|pysao| match pysao {
327 PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
328 PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
329 });
330 let sao_h_opt = sao_h.map(|pysao_h| match pysao_h {
331 PyArray2RC::Real(pysao_h_r) => pysao_h_r.to_owned_array().mapv(Complex::from),
332 PyArray2RC::Complex(pysao_h_c) => pysao_h_c.to_owned_array(),
333 });
334
335 match pydet_c.structure_constraint {
336 PyStructureConstraint::SpinConstraint(_) => {
337 let det_c = if augment_to_generalised {
338 pydet_c
339 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
340 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
341 .to_generalised()
342 } else {
343 pydet_c
344 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
345 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
346 };
347
348 let projected_sds = match &use_magnetic_group {
349 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
350 return Err(PyRuntimeError::new_err(
351 "Projection using corepresentations is not yet supported.",
352 ));
353 }
354 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
355 let mut sdp_driver = SlaterDeterminantProjectionDriver::<
356 UnitaryRepresentedSymmetryGroup,
357 C128,
358 _,
359 >::builder()
360 .parameters(&sdp_params)
361 .determinant(&det_c)
362 .symmetry_group(&pd_res)
363 .sao(sao_opt.as_ref())
364 .sao_h(sao_h_opt.as_ref())
365 .build()
366 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
367 py.detach(|| {
368 sdp_driver
369 .run()
370 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
371 })?;
372 sdp_driver
373 .result()
374 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
375 .projected_determinants()
376 .clone()
377 }
378 };
379 let (rows, (coefficientss, energies)): (Vec<_>, (Vec<_>, Vec<_>)) =
380 projected_sds
381 .iter()
382 .map(|(row, multidet_res)| {
383 let (coefficients, energy) = multidet_res
384 .as_ref()
385 .map(|multidet| {
386 let coefficients =
387 multidet.coefficients().iter().cloned().collect_vec();
388 let energy =
389 *multidet.energy().unwrap_or(&Complex::from(f64::NAN));
390 (coefficients, energy)
391 })
392 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
393 Ok::<_, PyErr>((row.to_string(), (coefficients, energy)))
394 })
395 .collect::<Result<Vec<_>, _>>()?
396 .into_iter()
397 .unzip();
398 let basis = projected_sds
399 .iter()
400 .next()
401 .and_then(|(_, multidet_res)| {
402 multidet_res.as_ref().ok().and_then(|multidet| {
403 multidet
404 .basis()
405 .iter()
406 .map(|det_res| det_res.and_then(|det| det.to_python(py)))
407 .collect::<Result<Vec<_>, _>>()
408 .ok()
409 })
410 })
411 .ok_or_else(|| {
412 PyRuntimeError::new_err(
413 "Unable to obtain the basis of Slater determinants.".to_string(),
414 )
415 })?;
416 let coefficientss_arr = Array2::from_shape_vec(
417 (basis.len(), coefficientss.len()).f(),
418 coefficientss.into_iter().flatten().collect_vec(),
419 )
420 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
421 .to_pyarray(py);
422 let energies_arr = Array1::from_vec(energies).to_pyarray(py);
423 let density_matrices =
424 match (density_matrix_calculation_thresholds, sao_opt.as_ref()) {
425 (Some((thresh_offdiag, thresh_zeroov)), Some(sao)) => Some(
426 projected_sds
427 .iter()
428 .map(|(_, multidet_res)| {
429 multidet_res
430 .as_ref()
431 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
432 .and_then(|multidet| {
433 multidet
434 .density_matrix(
435 &sao.view(),
436 thresh_offdiag,
437 thresh_zeroov,
438 true,
439 )
440 .map(|denmat| denmat.to_pyarray(py))
441 .map_err(|err| {
442 PyRuntimeError::new_err(err.to_string())
443 })
444 })
445 })
446 .collect::<Result<Vec<_>, _>>()?,
447 ),
448 _ => None,
449 };
450
451 let pymultidet = PyMultiDeterminantsComplex::new(
452 basis,
453 coefficientss_arr,
454 energies_arr,
455 density_matrices,
456 pydet_c.threshold,
457 )
458 .into_py_any(py)?;
459 Ok((rows, pymultidet))
460 }
461 PyStructureConstraint::SpinOrbitCoupled(_) => {
462 let det_c = pydet_c
463 .to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
464 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
465
466 let projected_sds = match &use_magnetic_group {
467 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
468 return Err(PyRuntimeError::new_err(
469 "Projection using corepresentations is not yet supported.",
470 ));
471 }
472 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
473 let mut sdp_driver = SlaterDeterminantProjectionDriver::<
474 UnitaryRepresentedSymmetryGroup,
475 C128,
476 _,
477 >::builder()
478 .parameters(&sdp_params)
479 .determinant(&det_c)
480 .symmetry_group(&pd_res)
481 .sao(sao_opt.as_ref())
482 .sao_h(sao_h_opt.as_ref())
483 .build()
484 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
485 py.detach(|| {
486 sdp_driver
487 .run()
488 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
489 })?;
490 sdp_driver
491 .result()
492 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
493 .projected_determinants()
494 .clone()
495 }
496 };
497 let (rows, (coefficientss, energies)): (Vec<_>, (Vec<_>, Vec<_>)) =
498 projected_sds
499 .iter()
500 .map(|(row, multidet_res)| {
501 let (coefficients, energy) = multidet_res
502 .as_ref()
503 .map(|multidet| {
504 let coefficients =
505 multidet.coefficients().iter().cloned().collect_vec();
506 let energy =
507 *multidet.energy().unwrap_or(&Complex::from(f64::NAN));
508 (coefficients, energy)
509 })
510 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
511 Ok::<_, PyErr>((row.to_string(), (coefficients, energy)))
512 })
513 .collect::<Result<Vec<_>, _>>()?
514 .into_iter()
515 .unzip();
516 let basis = projected_sds
517 .iter()
518 .next()
519 .and_then(|(_, multidet_res)| {
520 multidet_res.as_ref().ok().and_then(|multidet| {
521 multidet
522 .basis()
523 .iter()
524 .map(|det_res| det_res.and_then(|det| det.to_python(py)))
525 .collect::<Result<Vec<_>, _>>()
526 .ok()
527 })
528 })
529 .ok_or_else(|| {
530 PyRuntimeError::new_err(
531 "Unable to obtain the basis of Slater determinants.".to_string(),
532 )
533 })?;
534 let coefficientss_arr = Array2::from_shape_vec(
535 (basis.len(), coefficientss.len()).f(),
536 coefficientss.into_iter().flatten().collect_vec(),
537 )
538 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
539 .to_pyarray(py);
540 let energies_arr = Array1::from_vec(energies).to_pyarray(py);
541 let density_matrices =
542 match (density_matrix_calculation_thresholds, sao_opt.as_ref()) {
543 (Some((thresh_offdiag, thresh_zeroov)), Some(sao)) => Some(
544 projected_sds
545 .iter()
546 .map(|(_, multidet_res)| {
547 multidet_res
548 .as_ref()
549 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
550 .and_then(|multidet| {
551 multidet
552 .density_matrix(
553 &sao.view(),
554 thresh_offdiag,
555 thresh_zeroov,
556 true,
557 )
558 .map(|denmat| denmat.to_pyarray(py))
559 .map_err(|err| {
560 PyRuntimeError::new_err(err.to_string())
561 })
562 })
563 })
564 .collect::<Result<Vec<_>, _>>()?,
565 ),
566 _ => None,
567 };
568
569 let pymultidet = PyMultiDeterminantsComplex::new(
570 basis,
571 coefficientss_arr,
572 energies_arr,
573 density_matrices,
574 pydet_c.threshold,
575 )
576 .into_py_any(py)?;
577 Ok((rows, pymultidet))
578 }
579 }
580 }
581 }
582}