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!(tmatss.len(), self.structure_constraint.n_explicit_comps_per_coefficient_matrix());
65
66 let new_coefficients = self
67 .coefficients
68 .iter()
69 .map(|old_coeff| {
70 let t_p_comp_blocks = component_boundary_indices
71 .iter()
72 .zip(self.baos.iter())
73 .zip(tmatss.iter())
74 .map(|(((comp_start, comp_end), bao), tmats)| {
75 let old_coeff_comp: Array2<T> =
76 old_coeff.slice(s![*comp_start..*comp_end, ..]).to_owned();
77 let p_coeff = if let Some(p) = perm {
78 permute_array_by_atoms(&old_coeff_comp, p, &[Axis(0)], *bao)
79 } else {
80 old_coeff_comp.clone()
81 };
82 let pbao = if let Some(p) = perm {
83 bao.permute(p)
84 .map_err(|err| TransformationError(err.to_string()))?
85 } else {
86 (*bao).clone()
87 };
88 let t_p_blocks = pbao
89 .shell_boundary_indices()
90 .into_iter()
91 .zip(tmats.iter())
92 .map(|((shl_start, shl_end), tmat)| {
93 tmat.dot(&p_coeff.slice(s![shl_start..shl_end, ..]))
94 })
95 .collect::<Vec<_>>();
96 Ok(t_p_blocks)
97 })
98 .collect::<Result<Vec<Vec<Array2<T>>>, _>>()?
99 .into_iter()
100 .flatten()
101 .collect_vec();
102
103 concatenate(
104 Axis(0),
105 &t_p_comp_blocks
106 .iter()
107 .map(|t_p_block| t_p_block.view())
108 .collect::<Vec<_>>(),
109 )
110 .map_err(|err| {
111 TransformationError(format!(
112 "Unable to concatenate the transformed blocks: {err}."
113 ))
114 })
115 })
116 .collect::<Result<Vec<Array2<T>>, _>>()?;
117 self.coefficients = new_coefficients;
118 Ok(self)
119 }
120}
121
122impl<'a> SpinUnitaryTransformable for SlaterDeterminant<'a, f64, SpinConstraint> {
131 fn transform_spin_mut(
138 &mut self,
139 dmat: &Array2<Complex<f64>>,
140 ) -> Result<&mut Self, TransformationError> {
141 let cdmat = dmat.view().split_complex();
142 if approx::relative_ne!(
143 cdmat.im.map(|x| x.powi(2)).sum().sqrt(),
144 0.0,
145 epsilon = 1e-14,
146 max_relative = 1e-14,
147 ) {
148 log::error!("Spin transformation matrix is complex-valued:\n{dmat}");
149 Err(TransformationError(
150 "Complex spin transformations cannot be performed with real coefficients."
151 .to_string(),
152 ))
153 } else {
154 let rdmat = cdmat.re.to_owned();
155 match self.structure_constraint {
156 SpinConstraint::Restricted(_) => {
157 if approx::relative_eq!(
158 (&rdmat - Array2::<f64>::eye(2))
159 .map(|x| x.abs().powi(2))
160 .sum()
161 .sqrt(),
162 0.0,
163 epsilon = 1e-14,
164 max_relative = 1e-14,
165 ) {
166 Ok(self)
168 } else if approx::relative_eq!(
169 (&rdmat + Array2::<f64>::eye(2))
170 .map(|x| x.abs().powi(2))
171 .sum()
172 .sqrt(),
173 0.0,
174 epsilon = 1e-14,
175 max_relative = 1e-14,
176 ) {
177 self.coefficients
179 .iter_mut()
180 .for_each(|coeff| *coeff *= -1.0);
181 Ok(self)
182 } else {
183 log::error!("Unsupported spin transformation matrix:\n{}", &rdmat);
184 Err(TransformationError(
185 "Only the identity or negative identity spin transformations are possible with restricted spin constraint."
186 .to_string(),
187 ))
188 }
189 }
190 SpinConstraint::Unrestricted(nspins, increasingm) => {
191 if nspins != 2 {
194 return Err(TransformationError(
195 "Only two-component spinor transformations are supported for now."
196 .to_string(),
197 ));
198 }
199 let dmat_y = array![[0.0, -1.0], [1.0, 0.0]];
200 if approx::relative_eq!(
201 (&rdmat - Array2::<f64>::eye(2))
202 .map(|x| x.abs().powi(2))
203 .sum()
204 .sqrt(),
205 0.0,
206 epsilon = 1e-14,
207 max_relative = 1e-14,
208 ) {
209 Ok(self)
211 } else if approx::relative_eq!(
212 (&rdmat + Array2::<f64>::eye(2))
213 .map(|x| x.abs().powi(2))
214 .sum()
215 .sqrt(),
216 0.0,
217 epsilon = 1e-14,
218 max_relative = 1e-14,
219 ) {
220 self.coefficients
222 .iter_mut()
223 .for_each(|coeff| *coeff = -coeff.clone());
224 Ok(self)
225 } else if approx::relative_eq!(
226 (&rdmat - &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
227 0.0,
228 epsilon = 1e-14,
229 max_relative = 1e-14,
230 ) {
231 let new_coefficients = if increasingm {
233 vec![self.coefficients[1].clone(), -self.coefficients[0].clone()]
234 } else {
235 vec![-self.coefficients[1].clone(), self.coefficients[0].clone()]
236 };
237 let new_occupations =
238 vec![self.occupations[1].clone(), self.occupations[0].clone()];
239 self.coefficients = new_coefficients;
240 self.occupations = new_occupations;
241 Ok(self)
242 } else if approx::relative_eq!(
243 (&rdmat + &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
244 0.0,
245 epsilon = 1e-14,
246 max_relative = 1e-14,
247 ) {
248 let new_coefficients = if increasingm {
250 vec![-self.coefficients[1].clone(), self.coefficients[0].clone()]
251 } else {
252 vec![self.coefficients[1].clone(), -self.coefficients[0].clone()]
253 };
254 let new_occupations =
255 vec![self.occupations[1].clone(), self.occupations[0].clone()];
256 self.coefficients = new_coefficients;
257 self.occupations = new_occupations;
258 Ok(self)
259 } else {
260 log::error!("Unsupported spin transformation matrix:\n{rdmat}");
261 Err(TransformationError(
262 "Only the identity or πy spin transformations are possible with unrestricted spin constraint."
263 .to_string(),
264 ))
265 }
266 }
267 SpinConstraint::Generalised(nspins, increasingm) => {
268 if nspins != 2 {
269 return Err(TransformationError(
270 "Only two-component spinor transformations are supported for now."
271 .to_string(),
272 ));
273 }
274
275 let nspatial_set = self
276 .baos
277 .iter()
278 .map(|bao| bao.n_funcs())
279 .collect::<HashSet<usize>>();
280 if nspatial_set.len() != 1 {
281 return Err(TransformationError("Both explicit components in the generalised spin constraint must have the same number of spatial AO basis functions.".to_string()));
282 }
283 let nspatial = *nspatial_set.iter().next().ok_or_else(|| {
284 TransformationError(
285 "Unable to obtain the number of spatial AO basis functions."
286 .to_string(),
287 )
288 })?;
289 let new_coefficients = self
290 .coefficients
291 .iter()
292 .map(|old_coeff| {
293 if !increasingm {
294 let a_coeff = old_coeff.slice(s![0..nspatial, ..]).to_owned();
295 let b_coeff = old_coeff.slice(s![nspatial..2 * nspatial, ..]).to_owned();
296 let t_a_coeff = &a_coeff * rdmat[[0, 0]] + &b_coeff * rdmat[[0, 1]];
297 let t_b_coeff = &a_coeff * rdmat[[1, 0]] + &b_coeff * rdmat[[1, 1]];
298 concatenate(Axis(0), &[t_a_coeff.view(), t_b_coeff.view()]).expect(
299 "Unable to concatenate the transformed rows for the various shells.",
300 )
301 } else {
302 let b_coeff = old_coeff.slice(s![0..nspatial, ..]).to_owned();
303 let a_coeff = old_coeff.slice(s![nspatial..2 * nspatial, ..]).to_owned();
304 let t_a_coeff = &a_coeff * rdmat[[0, 0]] + &b_coeff * rdmat[[0, 1]];
305 let t_b_coeff = &a_coeff * rdmat[[1, 0]] + &b_coeff * rdmat[[1, 1]];
306 concatenate(Axis(0), &[t_b_coeff.view(), t_a_coeff.view()]).expect(
307 "Unable to concatenate the transformed rows for the various shells.",
308 )
309 }
310 })
311 .collect::<Vec<Array2<f64>>>();
312 self.coefficients = new_coefficients;
313 Ok(self)
314 }
315 }
316 }
317 }
318}
319
320impl<'a, T> SpinUnitaryTransformable for SlaterDeterminant<'a, Complex<T>, SpinConstraint>
325where
326 T: Clone,
327 Complex<T>: ComplexFloat<Real = T>
328 + LinalgScalar
329 + ScalarOperand
330 + Lapack
331 + Mul<Complex<T>, Output = Complex<T>>
332 + Mul<Complex<f64>, Output = Complex<T>>,
333{
334 fn transform_spin_mut(
346 &mut self,
347 dmat: &Array2<Complex<f64>>,
348 ) -> Result<&mut Self, TransformationError> {
349 match self.structure_constraint {
350 SpinConstraint::Restricted(_) => {
351 if approx::relative_eq!(
352 (dmat - Array2::<Complex<f64>>::eye(2))
353 .map(|x| x.abs().powi(2))
354 .sum()
355 .sqrt(),
356 0.0,
357 epsilon = 1e-14,
358 max_relative = 1e-14,
359 ) {
360 Ok(self)
362 } else if approx::relative_eq!(
363 (dmat + Array2::<Complex<f64>>::eye(2))
364 .map(|x| x.abs().powi(2))
365 .sum()
366 .sqrt(),
367 0.0,
368 epsilon = 1e-14,
369 max_relative = 1e-14,
370 ) {
371 self.coefficients
373 .iter_mut()
374 .for_each(|coeff| *coeff = -coeff.clone());
375 Ok(self)
376 } else {
377 log::error!("Unsupported spin transformation matrix:\n{}", dmat);
378 Err(TransformationError(
379 "Only the identity or negative identity spin transformations are possible with restricted spin constraint."
380 .to_string(),
381 ))
382 }
383 }
384 SpinConstraint::Unrestricted(nspins, increasingm) => {
385 if nspins != 2 {
388 return Err(TransformationError(
389 "Only two-component spinor transformations are supported for now."
390 .to_string(),
391 ));
392 }
393 let dmat_y = array![
394 [Complex::from(0.0), Complex::from(-1.0)],
395 [Complex::from(1.0), Complex::from(0.0)],
396 ];
397 if approx::relative_eq!(
398 (dmat - Array2::<Complex<f64>>::eye(2))
399 .map(|x| x.abs().powi(2))
400 .sum()
401 .sqrt(),
402 0.0,
403 epsilon = 1e-14,
404 max_relative = 1e-14,
405 ) {
406 Ok(self)
408 } else if approx::relative_eq!(
409 (dmat + Array2::<Complex<f64>>::eye(2))
410 .map(|x| x.abs().powi(2))
411 .sum()
412 .sqrt(),
413 0.0,
414 epsilon = 1e-14,
415 max_relative = 1e-14,
416 ) {
417 self.coefficients
419 .iter_mut()
420 .for_each(|coeff| *coeff = -coeff.clone());
421 Ok(self)
422 } else if approx::relative_eq!(
423 (dmat - &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
424 0.0,
425 epsilon = 1e-14,
426 max_relative = 1e-14,
427 ) {
428 let new_coefficients = if increasingm {
430 vec![self.coefficients[1].clone(), -self.coefficients[0].clone()]
431 } else {
432 vec![-self.coefficients[1].clone(), self.coefficients[0].clone()]
433 };
434 let new_occupations =
435 vec![self.occupations[1].clone(), self.occupations[0].clone()];
436 self.coefficients = new_coefficients;
437 self.occupations = new_occupations;
438 Ok(self)
439 } else if approx::relative_eq!(
440 (dmat + &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
441 0.0,
442 epsilon = 1e-14,
443 max_relative = 1e-14,
444 ) {
445 let new_coefficients = if increasingm {
447 vec![-self.coefficients[1].clone(), self.coefficients[0].clone()]
448 } else {
449 vec![self.coefficients[1].clone(), -self.coefficients[0].clone()]
450 };
451 let new_occupations =
452 vec![self.occupations[1].clone(), self.occupations[0].clone()];
453 self.coefficients = new_coefficients;
454 self.occupations = new_occupations;
455 Ok(self)
456 } else {
457 log::error!("Unsupported spin transformation matrix:\n{dmat}");
458 Err(TransformationError(
459 "Only the identity or πy spin transformations are possible with unrestricted spin constraint."
460 .to_string(),
461 ))
462 }
463 }
464 SpinConstraint::Generalised(nspins, increasingm) => {
465 if nspins != 2 {
466 panic!("Only two-component spinor transformations are supported for now.");
467 }
468
469 let nspatial_set = self
470 .baos
471 .iter()
472 .map(|bao| bao.n_funcs())
473 .collect::<HashSet<usize>>();
474 if nspatial_set.len() != 1 {
475 return Err(TransformationError("Both explicit components in the generalised spin constraint must have the same number of spatial AO basis functions.".to_string()));
476 }
477 let nspatial = *nspatial_set.iter().next().ok_or_else(|| {
478 TransformationError(
479 "Unable to obtain the number of spatial AO basis functions.".to_string(),
480 )
481 })?;
482
483 let new_coefficients = self
484 .coefficients
485 .iter()
486 .map(|old_coeff| {
487 if increasingm {
488 let b_coeff = old_coeff.slice(s![0..nspatial, ..]).to_owned();
489 let a_coeff = old_coeff.slice(s![nspatial..2 * nspatial, ..]).to_owned();
490 let t_a_coeff = &a_coeff * dmat[[0, 0]] + &b_coeff * dmat[[0, 1]];
491 let t_b_coeff = &a_coeff * dmat[[1, 0]] + &b_coeff * dmat[[1, 1]];
492 concatenate(Axis(0), &[t_b_coeff.view(), t_a_coeff.view()]).expect(
493 "Unable to concatenate the transformed rows for the various shells.",
494 )
495 } else {
496 let a_coeff = old_coeff.slice(s![0..nspatial, ..]).to_owned();
497 let b_coeff = old_coeff.slice(s![nspatial..2 * nspatial, ..]).to_owned();
498 let t_a_coeff = &a_coeff * dmat[[0, 0]] + &b_coeff * dmat[[0, 1]];
499 let t_b_coeff = &a_coeff * dmat[[1, 0]] + &b_coeff * dmat[[1, 1]];
500 concatenate(Axis(0), &[t_a_coeff.view(), t_b_coeff.view()]).expect(
501 "Unable to concatenate the transformed rows for the various shells.",
502 )
503 }
504 })
505 .collect::<Vec<Array2<Complex<T>>>>();
506 self.coefficients = new_coefficients;
507 Ok(self)
508 }
509 }
510 }
511}
512
513impl<'a, T> DefaultTimeReversalTransformable for SlaterDeterminant<'a, T, SpinConstraint> where
517 T: ComplexFloat + Lapack
518{
519}
520
521impl<'a, T> SymmetryTransformable for SlaterDeterminant<'a, T, SpinConstraint>
525where
526 T: ComplexFloat + Lapack,
527 SlaterDeterminant<'a, T, SpinConstraint>:
528 SpatialUnitaryTransformable + SpinUnitaryTransformable + TimeReversalTransformable,
529{
530 fn sym_permute_sites_spatial(
531 &self,
532 symop: &SymmetryOperation,
533 ) -> Result<Permutation<usize>, TransformationError> {
534 if (symop.generating_element.threshold().log10() - self.mol.threshold.log10()).abs() >= 3.0
535 {
536 log::warn!(
537 "Symmetry operation threshold ({:.3e}) and molecule threshold ({:.3e}) \
538 differ by more than three orders of magnitudes.",
539 symop.generating_element.threshold(),
540 self.mol.threshold
541 )
542 }
543 symop
544 .act_permute(&self.mol.molecule_ordinary_atoms())
545 .ok_or(TransformationError(format!(
546 "Unable to determine the atom permutation corresponding to the operation `{symop}`.",
547 )))
548 }
549}
550
551impl<'a, T> SpatialUnitaryTransformable for SlaterDeterminant<'a, T, SpinOrbitCoupled>
559where
560 T: ComplexFloat + Lapack,
561{
562 fn transform_spatial_mut(
563 &mut self,
564 _rmat: &Array2<f64>,
565 _perm: Option<&Permutation<usize>>,
566 ) -> Result<&mut Self, TransformationError> {
567 Err(TransformationError(
568 "Unable to apply only spatial transformations to a spin--orbit-coupled determinant."
569 .to_string(),
570 ))
571 }
572}
573
574impl<'a, T> SpinUnitaryTransformable for SlaterDeterminant<'a, T, SpinOrbitCoupled>
578where
579 T: ComplexFloat + Lapack,
580{
581 fn transform_spin_mut(
582 &mut self,
583 _dmat: &Array2<Complex<f64>>,
584 ) -> Result<&mut Self, TransformationError> {
585 Err(TransformationError(
586 "Unable to apply only spin transformations to a spin--orbit-coupled determinant."
587 .to_string(),
588 ))
589 }
590}
591
592impl<'a> TimeReversalTransformable for SlaterDeterminant<'a, Complex<f64>, SpinOrbitCoupled> {
596 fn transform_timerev_mut(&mut self) -> Result<&mut Self, TransformationError> {
597 let t_element = SymmetryElement::builder()
598 .threshold(1e-12)
599 .proper_order(ElementOrder::Int(1))
600 .proper_power(1)
601 .raw_axis(Vector3::new(0.0, 0.0, 1.0))
602 .kind(TRROT)
603 .rotation_group(RotationGroup::SU2(true))
604 .build()
605 .unwrap();
606 let t = SymmetryOperation::builder()
607 .generating_element(t_element)
608 .power(1)
609 .build()
610 .unwrap();
611 let tmatss: Vec<Vec<Array2<Complex<f64>>>> =
612 assemble_spinor_rotation_matrices(&self.baos, &t, None)
613 .map_err(|err| TransformationError(err.to_string()))?
614 .iter()
615 .map(|tmats| {
616 tmats
617 .iter()
618 .map(|tmat| tmat.mapv(|x| x.into()))
619 .collect::<Vec<_>>()
620 })
621 .collect::<Vec<_>>();
622
623 let component_boundary_indices = self
624 .baos
625 .iter()
626 .scan(0, |acc, bao| {
627 let start_index = *acc;
628 *acc += bao.n_funcs();
629 Some((start_index, *acc))
630 })
631 .collect::<Vec<_>>();
632 assert_eq!(tmatss.len(), component_boundary_indices.len());
633 assert_eq!(tmatss.len(), self.structure_constraint.n_explicit_comps_per_coefficient_matrix());
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 .iter()
722 .map(|tmats| {
723 tmats
724 .iter()
725 .map(|tmat| tmat.mapv(|x| x.into()))
726 .collect::<Vec<_>>()
727 })
728 .collect::<Vec<_>>();
729 let component_boundary_indices = self
730 .baos
731 .iter()
732 .scan(0, |acc, bao| {
733 let start_index = *acc;
734 *acc += bao.n_funcs();
735 Some((start_index, *acc))
736 })
737 .collect::<Vec<_>>();
738 assert_eq!(tmatss.len(), component_boundary_indices.len());
739 assert_eq!(tmatss.len(), self.structure_constraint.n_explicit_comps_per_coefficient_matrix());
740
741 if symop.contains_time_reversal() {
743 self.transform_cc_mut()?;
748 }
749
750 let new_coefficients = self
751 .coefficients
752 .iter()
753 .map(|old_coeff| {
754 let t_p_comp_blocks = component_boundary_indices
755 .iter()
756 .zip(self.baos.iter())
757 .zip(tmatss.iter())
758 .map(|(((comp_start, comp_end), bao), tmats)| {
759 let old_coeff_comp: Array2<_> =
760 old_coeff.slice(s![*comp_start..*comp_end, ..]).to_owned();
761 let p_coeff =
762 permute_array_by_atoms(&old_coeff_comp, &perm, &[Axis(0)], *bao);
763 let pbao = bao
764 .permute(&perm)
765 .map_err(|err| TransformationError(err.to_string()))?;
766 let t_p_blocks = pbao
767 .shell_boundary_indices()
768 .into_iter()
769 .zip(tmats.iter())
770 .map(|((shl_start, shl_end), tmat)| {
771 tmat.dot(&p_coeff.slice(s![shl_start..shl_end, ..]))
772 })
773 .collect::<Vec<_>>();
774 Ok(t_p_blocks)
775 })
776 .collect::<Result<Vec<Vec<Array2<_>>>, _>>()?
777 .into_iter()
778 .flatten()
779 .collect_vec();
780
781 concatenate(
782 Axis(0),
783 &t_p_comp_blocks
784 .iter()
785 .map(|t_p_block| t_p_block.view())
786 .collect::<Vec<_>>(),
787 )
788 .map_err(|err| {
789 TransformationError(format!(
790 "Unable to concatenate the transformed blocks: {err}."
791 ))
792 })
793 })
794 .collect::<Result<Vec<Array2<Complex<f64>>>, _>>()?;
795 self.coefficients = new_coefficients;
796
797 Ok(self)
798 }
799}
800
801impl<'a, T, SC> ComplexConjugationTransformable for SlaterDeterminant<'a, T, SC>
810where
811 T: ComplexFloat + Lapack,
812 SC: StructureConstraint + Clone + fmt::Display,
813{
814 fn transform_cc_mut(&mut self) -> Result<&mut Self, TransformationError> {
816 self.coefficients
817 .iter_mut()
818 .for_each(|coeff| coeff.mapv_inplace(|x| x.conj()));
819 self.complex_conjugated = !self.complex_conjugated;
820 Ok(self)
821 }
822}