1use std::collections::HashSet;
4use std::fmt;
5use std::ops::Mul;
6
7use approx;
8use itertools::Itertools;
9use nalgebra::Vector3;
10use ndarray::{Array2, Axis, LinalgScalar, ScalarOperand, array, concatenate, s};
11use ndarray_linalg::types::Lapack;
12use num_complex::{Complex, ComplexFloat};
13
14use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled, StructureConstraint};
15use crate::permutation::{IntoPermutation, PermutableCollection, Permutation};
16use crate::symmetry::symmetry_element::symmetry_operation::SpecialSymmetryTransformation;
17use crate::symmetry::symmetry_element::{RotationGroup, SymmetryElement, SymmetryOperation, TRROT};
18use crate::symmetry::symmetry_element_order::ElementOrder;
19use crate::symmetry::symmetry_transformation::{
20 ComplexConjugationTransformable, DefaultTimeReversalTransformable, SpatialUnitaryTransformable,
21 SpinUnitaryTransformable, SymmetryTransformable, TimeReversalTransformable,
22 TransformationError, assemble_sh_rotation_3d_matrices, assemble_spinor_rotation_matrices,
23 permute_array_by_atoms,
24};
25use crate::target::determinant::SlaterDeterminant;
26
27impl<'a, T> SpatialUnitaryTransformable for SlaterDeterminant<'a, T, SpinConstraint>
35where
36 T: ComplexFloat + LinalgScalar + ScalarOperand + Copy + Lapack,
37 f64: Into<T>,
38{
39 fn transform_spatial_mut(
40 &mut self,
41 rmat: &Array2<f64>,
42 perm: Option<&Permutation<usize>>,
43 ) -> Result<&mut Self, TransformationError> {
44 let tmatss: Vec<Vec<Array2<T>>> = assemble_sh_rotation_3d_matrices(&self.baos, rmat, perm)
45 .map_err(|err| TransformationError(err.to_string()))?
46 .iter()
47 .map(|tmats| {
48 tmats
49 .iter()
50 .map(|tmat| tmat.mapv(|x| x.into()))
51 .collect::<Vec<_>>()
52 })
53 .collect::<Vec<_>>();
54 let component_boundary_indices = self
55 .baos
56 .iter()
57 .scan(0, |acc, bao| {
58 let start_index = *acc;
59 *acc += bao.n_funcs();
60 Some((start_index, *acc))
61 })
62 .collect::<Vec<_>>();
63 assert_eq!(tmatss.len(), component_boundary_indices.len());
64 assert_eq!(
65 tmatss.len(),
66 self.structure_constraint
67 .n_explicit_comps_per_coefficient_matrix()
68 );
69
70 let new_coefficients = self
71 .coefficients
72 .iter()
73 .map(|old_coeff| {
74 let t_p_comp_blocks = component_boundary_indices
75 .iter()
76 .zip(self.baos.iter())
77 .zip(tmatss.iter())
78 .map(|(((comp_start, comp_end), bao), tmats)| {
79 let old_coeff_comp: Array2<T> =
80 old_coeff.slice(s![*comp_start..*comp_end, ..]).to_owned();
81 let p_coeff = if let Some(p) = perm {
82 permute_array_by_atoms(&old_coeff_comp, p, &[Axis(0)], bao)
83 } else {
84 old_coeff_comp.clone()
85 };
86 let pbao = if let Some(p) = perm {
87 bao.permute(p)
88 .map_err(|err| TransformationError(err.to_string()))?
89 } else {
90 (*bao).clone()
91 };
92 let t_p_blocks = pbao
93 .shell_boundary_indices()
94 .into_iter()
95 .zip(tmats.iter())
96 .map(|((shl_start, shl_end), tmat)| {
97 tmat.dot(&p_coeff.slice(s![shl_start..shl_end, ..]))
98 })
99 .collect::<Vec<_>>();
100 Ok(t_p_blocks)
101 })
102 .collect::<Result<Vec<Vec<Array2<T>>>, _>>()?
103 .into_iter()
104 .flatten()
105 .collect_vec();
106
107 concatenate(
108 Axis(0),
109 &t_p_comp_blocks
110 .iter()
111 .map(|t_p_block| t_p_block.view())
112 .collect::<Vec<_>>(),
113 )
114 .map_err(|err| {
115 TransformationError(format!(
116 "Unable to concatenate the transformed blocks: {err}."
117 ))
118 })
119 })
120 .collect::<Result<Vec<Array2<T>>, _>>()?;
121 self.coefficients = new_coefficients;
122 Ok(self)
123 }
124}
125
126impl<'a> SpinUnitaryTransformable for SlaterDeterminant<'a, f64, SpinConstraint> {
135 fn transform_spin_mut(
142 &mut self,
143 dmat: &Array2<Complex<f64>>,
144 ) -> Result<&mut Self, TransformationError> {
145 let cdmat = dmat.view().split_complex();
146 if approx::relative_ne!(
147 cdmat.im.map(|x| x.powi(2)).sum().sqrt(),
148 0.0,
149 epsilon = 1e-14,
150 max_relative = 1e-14,
151 ) {
152 log::error!("Spin transformation matrix is complex-valued:\n{dmat}");
153 Err(TransformationError(
154 "Complex spin transformations cannot be performed with real coefficients."
155 .to_string(),
156 ))
157 } else {
158 let rdmat = cdmat.re.to_owned();
159 match self.structure_constraint {
160 SpinConstraint::Restricted(_) => {
161 if approx::relative_eq!(
162 (&rdmat - Array2::<f64>::eye(2))
163 .map(|x| x.abs().powi(2))
164 .sum()
165 .sqrt(),
166 0.0,
167 epsilon = 1e-14,
168 max_relative = 1e-14,
169 ) {
170 Ok(self)
172 } else if approx::relative_eq!(
173 (&rdmat + Array2::<f64>::eye(2))
174 .map(|x| x.abs().powi(2))
175 .sum()
176 .sqrt(),
177 0.0,
178 epsilon = 1e-14,
179 max_relative = 1e-14,
180 ) {
181 self.coefficients
183 .iter_mut()
184 .for_each(|coeff| *coeff *= -1.0);
185 Ok(self)
186 } else {
187 log::error!("Unsupported spin transformation matrix:\n{}", &rdmat);
188 Err(TransformationError(
189 "Only the identity or negative identity spin transformations are possible with restricted spin constraint."
190 .to_string(),
191 ))
192 }
193 }
194 SpinConstraint::Unrestricted(nspins, increasingm) => {
195 if nspins != 2 {
198 return Err(TransformationError(
199 "Only two-component spinor transformations are supported for now."
200 .to_string(),
201 ));
202 }
203 let dmat_y = array![[0.0, -1.0], [1.0, 0.0]];
204 if approx::relative_eq!(
205 (&rdmat - Array2::<f64>::eye(2))
206 .map(|x| x.abs().powi(2))
207 .sum()
208 .sqrt(),
209 0.0,
210 epsilon = 1e-14,
211 max_relative = 1e-14,
212 ) {
213 Ok(self)
215 } else if approx::relative_eq!(
216 (&rdmat + Array2::<f64>::eye(2))
217 .map(|x| x.abs().powi(2))
218 .sum()
219 .sqrt(),
220 0.0,
221 epsilon = 1e-14,
222 max_relative = 1e-14,
223 ) {
224 self.coefficients
226 .iter_mut()
227 .for_each(|coeff| *coeff = -coeff.clone());
228 Ok(self)
229 } else if approx::relative_eq!(
230 (&rdmat - &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
231 0.0,
232 epsilon = 1e-14,
233 max_relative = 1e-14,
234 ) {
235 let new_coefficients = if increasingm {
237 vec![self.coefficients[1].clone(), -self.coefficients[0].clone()]
238 } else {
239 vec![-self.coefficients[1].clone(), self.coefficients[0].clone()]
240 };
241 let new_occupations =
242 vec![self.occupations[1].clone(), self.occupations[0].clone()];
243 self.coefficients = new_coefficients;
244 self.occupations = new_occupations;
245 Ok(self)
246 } else if approx::relative_eq!(
247 (&rdmat + &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
248 0.0,
249 epsilon = 1e-14,
250 max_relative = 1e-14,
251 ) {
252 let new_coefficients = if increasingm {
254 vec![-self.coefficients[1].clone(), self.coefficients[0].clone()]
255 } else {
256 vec![self.coefficients[1].clone(), -self.coefficients[0].clone()]
257 };
258 let new_occupations =
259 vec![self.occupations[1].clone(), self.occupations[0].clone()];
260 self.coefficients = new_coefficients;
261 self.occupations = new_occupations;
262 Ok(self)
263 } else {
264 log::error!("Unsupported spin transformation matrix:\n{rdmat}");
265 Err(TransformationError(
266 "Only the identity or πy spin transformations are possible with unrestricted spin constraint."
267 .to_string(),
268 ))
269 }
270 }
271 SpinConstraint::Generalised(nspins, increasingm) => {
272 if nspins != 2 {
273 return Err(TransformationError(
274 "Only two-component spinor transformations are supported for now."
275 .to_string(),
276 ));
277 }
278
279 let nspatial_set = self
280 .baos
281 .iter()
282 .map(|bao| bao.n_funcs())
283 .collect::<HashSet<usize>>();
284 if nspatial_set.len() != 1 {
285 return Err(TransformationError("Both explicit components in the generalised spin constraint must have the same number of spatial AO basis functions.".to_string()));
286 }
287 let nspatial = *nspatial_set.iter().next().ok_or_else(|| {
288 TransformationError(
289 "Unable to obtain the number of spatial AO basis functions."
290 .to_string(),
291 )
292 })?;
293 let new_coefficients = self
294 .coefficients
295 .iter()
296 .map(|old_coeff| {
297 if !increasingm {
298 let a_coeff = old_coeff.slice(s![0..nspatial, ..]).to_owned();
299 let b_coeff = old_coeff.slice(s![nspatial..2 * nspatial, ..]).to_owned();
300 let t_a_coeff = &a_coeff * rdmat[[0, 0]] + &b_coeff * rdmat[[0, 1]];
301 let t_b_coeff = &a_coeff * rdmat[[1, 0]] + &b_coeff * rdmat[[1, 1]];
302 concatenate(Axis(0), &[t_a_coeff.view(), t_b_coeff.view()]).expect(
303 "Unable to concatenate the transformed rows for the various shells.",
304 )
305 } else {
306 let b_coeff = old_coeff.slice(s![0..nspatial, ..]).to_owned();
307 let a_coeff = old_coeff.slice(s![nspatial..2 * nspatial, ..]).to_owned();
308 let t_a_coeff = &a_coeff * rdmat[[0, 0]] + &b_coeff * rdmat[[0, 1]];
309 let t_b_coeff = &a_coeff * rdmat[[1, 0]] + &b_coeff * rdmat[[1, 1]];
310 concatenate(Axis(0), &[t_b_coeff.view(), t_a_coeff.view()]).expect(
311 "Unable to concatenate the transformed rows for the various shells.",
312 )
313 }
314 })
315 .collect::<Vec<Array2<f64>>>();
316 self.coefficients = new_coefficients;
317 Ok(self)
318 }
319 }
320 }
321 }
322}
323
324impl<'a, T> SpinUnitaryTransformable for SlaterDeterminant<'a, Complex<T>, SpinConstraint>
329where
330 T: Clone,
331 Complex<T>: ComplexFloat<Real = T>
332 + LinalgScalar
333 + ScalarOperand
334 + Lapack
335 + Mul<Complex<T>, Output = Complex<T>>
336 + Mul<Complex<f64>, Output = Complex<T>>,
337{
338 fn transform_spin_mut(
350 &mut self,
351 dmat: &Array2<Complex<f64>>,
352 ) -> Result<&mut Self, TransformationError> {
353 match self.structure_constraint {
354 SpinConstraint::Restricted(_) => {
355 if approx::relative_eq!(
356 (dmat - Array2::<Complex<f64>>::eye(2))
357 .map(|x| x.abs().powi(2))
358 .sum()
359 .sqrt(),
360 0.0,
361 epsilon = 1e-14,
362 max_relative = 1e-14,
363 ) {
364 Ok(self)
366 } else if approx::relative_eq!(
367 (dmat + Array2::<Complex<f64>>::eye(2))
368 .map(|x| x.abs().powi(2))
369 .sum()
370 .sqrt(),
371 0.0,
372 epsilon = 1e-14,
373 max_relative = 1e-14,
374 ) {
375 self.coefficients
377 .iter_mut()
378 .for_each(|coeff| *coeff = -coeff.clone());
379 Ok(self)
380 } else {
381 log::error!("Unsupported spin transformation matrix:\n{}", dmat);
382 Err(TransformationError(
383 "Only the identity or negative identity spin transformations are possible with restricted spin constraint."
384 .to_string(),
385 ))
386 }
387 }
388 SpinConstraint::Unrestricted(nspins, increasingm) => {
389 if nspins != 2 {
392 return Err(TransformationError(
393 "Only two-component spinor transformations are supported for now."
394 .to_string(),
395 ));
396 }
397 let dmat_y = array![
398 [Complex::from(0.0), Complex::from(-1.0)],
399 [Complex::from(1.0), Complex::from(0.0)],
400 ];
401 if approx::relative_eq!(
402 (dmat - Array2::<Complex<f64>>::eye(2))
403 .map(|x| x.abs().powi(2))
404 .sum()
405 .sqrt(),
406 0.0,
407 epsilon = 1e-14,
408 max_relative = 1e-14,
409 ) {
410 Ok(self)
412 } else if approx::relative_eq!(
413 (dmat + Array2::<Complex<f64>>::eye(2))
414 .map(|x| x.abs().powi(2))
415 .sum()
416 .sqrt(),
417 0.0,
418 epsilon = 1e-14,
419 max_relative = 1e-14,
420 ) {
421 self.coefficients
423 .iter_mut()
424 .for_each(|coeff| *coeff = -coeff.clone());
425 Ok(self)
426 } else if approx::relative_eq!(
427 (dmat - &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
428 0.0,
429 epsilon = 1e-14,
430 max_relative = 1e-14,
431 ) {
432 let new_coefficients = if increasingm {
434 vec![self.coefficients[1].clone(), -self.coefficients[0].clone()]
435 } else {
436 vec![-self.coefficients[1].clone(), self.coefficients[0].clone()]
437 };
438 let new_occupations =
439 vec![self.occupations[1].clone(), self.occupations[0].clone()];
440 self.coefficients = new_coefficients;
441 self.occupations = new_occupations;
442 Ok(self)
443 } else if approx::relative_eq!(
444 (dmat + &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
445 0.0,
446 epsilon = 1e-14,
447 max_relative = 1e-14,
448 ) {
449 let new_coefficients = if increasingm {
451 vec![-self.coefficients[1].clone(), self.coefficients[0].clone()]
452 } else {
453 vec![self.coefficients[1].clone(), -self.coefficients[0].clone()]
454 };
455 let new_occupations =
456 vec![self.occupations[1].clone(), self.occupations[0].clone()];
457 self.coefficients = new_coefficients;
458 self.occupations = new_occupations;
459 Ok(self)
460 } else {
461 log::error!("Unsupported spin transformation matrix:\n{dmat}");
462 Err(TransformationError(
463 "Only the identity or πy spin transformations are possible with unrestricted spin constraint."
464 .to_string(),
465 ))
466 }
467 }
468 SpinConstraint::Generalised(nspins, increasingm) => {
469 if nspins != 2 {
470 panic!("Only two-component spinor transformations are supported for now.");
471 }
472
473 let nspatial_set = self
474 .baos
475 .iter()
476 .map(|bao| bao.n_funcs())
477 .collect::<HashSet<usize>>();
478 if nspatial_set.len() != 1 {
479 return Err(TransformationError("Both explicit components in the generalised spin constraint must have the same number of spatial AO basis functions.".to_string()));
480 }
481 let nspatial = *nspatial_set.iter().next().ok_or_else(|| {
482 TransformationError(
483 "Unable to obtain the number of spatial AO basis functions.".to_string(),
484 )
485 })?;
486
487 let new_coefficients = self
488 .coefficients
489 .iter()
490 .map(|old_coeff| {
491 if increasingm {
492 let b_coeff = old_coeff.slice(s![0..nspatial, ..]).to_owned();
493 let a_coeff = old_coeff.slice(s![nspatial..2 * nspatial, ..]).to_owned();
494 let t_a_coeff = &a_coeff * dmat[[0, 0]] + &b_coeff * dmat[[0, 1]];
495 let t_b_coeff = &a_coeff * dmat[[1, 0]] + &b_coeff * dmat[[1, 1]];
496 concatenate(Axis(0), &[t_b_coeff.view(), t_a_coeff.view()]).expect(
497 "Unable to concatenate the transformed rows for the various shells.",
498 )
499 } else {
500 let a_coeff = old_coeff.slice(s![0..nspatial, ..]).to_owned();
501 let b_coeff = old_coeff.slice(s![nspatial..2 * nspatial, ..]).to_owned();
502 let t_a_coeff = &a_coeff * dmat[[0, 0]] + &b_coeff * dmat[[0, 1]];
503 let t_b_coeff = &a_coeff * dmat[[1, 0]] + &b_coeff * dmat[[1, 1]];
504 concatenate(Axis(0), &[t_a_coeff.view(), t_b_coeff.view()]).expect(
505 "Unable to concatenate the transformed rows for the various shells.",
506 )
507 }
508 })
509 .collect::<Vec<Array2<Complex<T>>>>();
510 self.coefficients = new_coefficients;
511 Ok(self)
512 }
513 }
514 }
515}
516
517impl<'a, T> DefaultTimeReversalTransformable for SlaterDeterminant<'a, T, SpinConstraint> where
521 T: ComplexFloat + Lapack
522{
523}
524
525impl<'a, T> SymmetryTransformable for SlaterDeterminant<'a, T, SpinConstraint>
529where
530 T: ComplexFloat + Lapack,
531 SlaterDeterminant<'a, T, SpinConstraint>:
532 SpatialUnitaryTransformable + SpinUnitaryTransformable + TimeReversalTransformable,
533{
534 fn sym_permute_sites_spatial(
535 &self,
536 symop: &SymmetryOperation,
537 ) -> Result<Permutation<usize>, TransformationError> {
538 if (symop.generating_element.threshold().log10() - self.mol.threshold.log10()).abs() >= 3.0
539 {
540 log::warn!(
541 "Symmetry operation threshold ({:.3e}) and molecule threshold ({:.3e}) \
542 differ by more than three orders of magnitudes.",
543 symop.generating_element.threshold(),
544 self.mol.threshold
545 )
546 }
547 symop
548 .act_permute(&self.mol.molecule_ordinary_atoms())
549 .ok_or(TransformationError(format!(
550 "Unable to determine the atom permutation corresponding to the operation `{symop}`.",
551 )))
552 }
553}
554
555impl<'a, T> SpatialUnitaryTransformable for SlaterDeterminant<'a, T, SpinOrbitCoupled>
563where
564 T: ComplexFloat + Lapack,
565{
566 fn transform_spatial_mut(
567 &mut self,
568 _rmat: &Array2<f64>,
569 _perm: Option<&Permutation<usize>>,
570 ) -> Result<&mut Self, TransformationError> {
571 Err(TransformationError(
572 "Unable to apply only spatial transformations to a spin--orbit-coupled determinant."
573 .to_string(),
574 ))
575 }
576}
577
578impl<'a, T> SpinUnitaryTransformable for SlaterDeterminant<'a, T, SpinOrbitCoupled>
582where
583 T: ComplexFloat + Lapack,
584{
585 fn transform_spin_mut(
586 &mut self,
587 _dmat: &Array2<Complex<f64>>,
588 ) -> Result<&mut Self, TransformationError> {
589 Err(TransformationError(
590 "Unable to apply only spin transformations to a spin--orbit-coupled determinant."
591 .to_string(),
592 ))
593 }
594}
595
596impl<'a> TimeReversalTransformable for SlaterDeterminant<'a, Complex<f64>, SpinOrbitCoupled> {
600 fn transform_timerev_mut(&mut self) -> Result<&mut Self, TransformationError> {
601 let t_element = SymmetryElement::builder()
602 .threshold(1e-12)
603 .proper_order(ElementOrder::Int(1))
604 .proper_power(1)
605 .raw_axis(Vector3::new(0.0, 0.0, 1.0))
606 .kind(TRROT)
607 .rotation_group(RotationGroup::SU2(true))
608 .build()
609 .unwrap();
610 let t = SymmetryOperation::builder()
611 .generating_element(t_element)
612 .power(1)
613 .build()
614 .unwrap();
615 let tmatss: Vec<Vec<Array2<Complex<f64>>>> =
616 assemble_spinor_rotation_matrices(&self.baos, &t, None)
617 .map_err(|err| TransformationError(err.to_string()))?;
618
619 let component_boundary_indices = self
620 .baos
621 .iter()
622 .scan(0, |acc, bao| {
623 let start_index = *acc;
624 *acc += bao.n_funcs();
625 Some((start_index, *acc))
626 })
627 .collect::<Vec<_>>();
628 assert_eq!(tmatss.len(), component_boundary_indices.len());
629 assert_eq!(
630 tmatss.len(),
631 self.structure_constraint
632 .n_explicit_comps_per_coefficient_matrix()
633 );
634
635 let new_coefficients = self
636 .coefficients
637 .iter()
638 .map(|old_coeff| {
639 let t_comp_blocks = component_boundary_indices
640 .iter()
641 .zip(self.baos.iter())
642 .zip(tmatss.iter())
643 .map(|(((comp_start, comp_end), bao), tmats)| {
644 let old_coeff_comp: Array2<_> =
645 old_coeff.slice(s![*comp_start..*comp_end, ..]).to_owned();
646
647 let t_blocks = bao
648 .shell_boundary_indices()
649 .into_iter()
650 .zip(tmats.iter())
651 .map(|((shl_start, shl_end), tmat)| {
652 tmat.dot(&old_coeff_comp.slice(s![shl_start..shl_end, ..]))
653 })
654 .collect::<Vec<_>>();
655 Ok(t_blocks)
656 })
657 .collect::<Result<Vec<Vec<Array2<_>>>, _>>()?
658 .into_iter()
659 .flatten()
660 .collect_vec();
661
662 concatenate(
663 Axis(0),
664 &t_comp_blocks
665 .iter()
666 .map(|t_comp_block| t_comp_block.view())
667 .collect::<Vec<_>>(),
668 )
669 .map_err(|err| {
670 TransformationError(format!(
671 "Unable to concatenate the transformed blocks: {err}."
672 ))
673 })
674 })
675 .collect::<Result<Vec<Array2<Complex<f64>>>, _>>()?;
676 self.coefficients = new_coefficients;
677
678 self.transform_cc_mut()?;
679 Ok(self)
680 }
681}
682
683impl<'a> SymmetryTransformable for SlaterDeterminant<'a, Complex<f64>, SpinOrbitCoupled> {
687 fn sym_permute_sites_spatial(
691 &self,
692 symop: &SymmetryOperation,
693 ) -> Result<Permutation<usize>, TransformationError> {
694 if (symop.generating_element.threshold().log10() - self.mol.threshold.log10()).abs() >= 3.0
695 {
696 log::warn!(
697 "Symmetry operation threshold ({:.3e}) and molecule threshold ({:.3e}) \
698 differ by more than three orders of magnitudes.",
699 symop.generating_element.threshold(),
700 self.mol.threshold
701 )
702 }
703 symop
704 .act_permute(&self.mol.molecule_ordinary_atoms())
705 .ok_or(TransformationError(format!(
706 "Unable to determine the atom permutation corresponding to the operation `{symop}`.",
707 )))
708 }
709
710 fn sym_transform_spin_spatial_mut(
714 &mut self,
715 symop: &SymmetryOperation,
716 ) -> Result<&mut Self, TransformationError> {
717 let perm = self.sym_permute_sites_spatial(symop)?;
718 let tmatss: Vec<Vec<Array2<Complex<f64>>>> =
719 assemble_spinor_rotation_matrices(&self.baos, symop, Some(&perm))
720 .map_err(|err| TransformationError(err.to_string()))?;
721 let component_boundary_indices = self
722 .baos
723 .iter()
724 .scan(0, |acc, bao| {
725 let start_index = *acc;
726 *acc += bao.n_funcs();
727 Some((start_index, *acc))
728 })
729 .collect::<Vec<_>>();
730 assert_eq!(tmatss.len(), component_boundary_indices.len());
731 assert_eq!(
732 tmatss.len(),
733 self.structure_constraint
734 .n_explicit_comps_per_coefficient_matrix()
735 );
736
737 if symop.contains_time_reversal() {
739 self.transform_cc_mut()?;
744 }
745
746 let new_coefficients = self
747 .coefficients
748 .iter()
749 .map(|old_coeff| {
750 let t_p_comp_blocks = component_boundary_indices
751 .iter()
752 .zip(self.baos.iter())
753 .zip(tmatss.iter())
754 .map(|(((comp_start, comp_end), bao), tmats)| {
755 let old_coeff_comp: Array2<_> =
756 old_coeff.slice(s![*comp_start..*comp_end, ..]).to_owned();
757 let p_coeff =
758 permute_array_by_atoms(&old_coeff_comp, &perm, &[Axis(0)], bao);
759 let pbao = bao
760 .permute(&perm)
761 .map_err(|err| TransformationError(err.to_string()))?;
762 let t_p_blocks = pbao
763 .shell_boundary_indices()
764 .into_iter()
765 .zip(tmats.iter())
766 .map(|((shl_start, shl_end), tmat)| {
767 tmat.dot(&p_coeff.slice(s![shl_start..shl_end, ..]))
768 })
769 .collect::<Vec<_>>();
770 Ok(t_p_blocks)
771 })
772 .collect::<Result<Vec<Vec<Array2<_>>>, _>>()?
773 .into_iter()
774 .flatten()
775 .collect_vec();
776
777 concatenate(
778 Axis(0),
779 &t_p_comp_blocks
780 .iter()
781 .map(|t_p_block| t_p_block.view())
782 .collect::<Vec<_>>(),
783 )
784 .map_err(|err| {
785 TransformationError(format!(
786 "Unable to concatenate the transformed blocks: {err}."
787 ))
788 })
789 })
790 .collect::<Result<Vec<Array2<Complex<f64>>>, _>>()?;
791 self.coefficients = new_coefficients;
792
793 Ok(self)
794 }
795}
796
797impl<'a, T, SC> ComplexConjugationTransformable for SlaterDeterminant<'a, T, SC>
806where
807 T: ComplexFloat + Lapack,
808 SC: StructureConstraint + Clone + fmt::Display,
809{
810 fn transform_cc_mut(&mut self) -> Result<&mut Self, TransformationError> {
812 self.coefficients
813 .iter_mut()
814 .for_each(|coeff| coeff.mapv_inplace(|x| x.conj()));
815 self.complex_conjugated = !self.complex_conjugated;
816 Ok(self)
817 }
818}