1use std::collections::HashSet;
4use std::ops::Mul;
5
6use approx;
7use nalgebra::Vector3;
8use ndarray::{Array1, Array2, Axis, LinalgScalar, ScalarOperand, array, concatenate, s};
9use ndarray_linalg::types::Lapack;
10use num_complex::{Complex, ComplexFloat};
11
12use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled, StructureConstraint};
13use crate::permutation::{IntoPermutation, PermutableCollection, Permutation};
14use crate::symmetry::symmetry_element::symmetry_operation::SpecialSymmetryTransformation;
15use crate::symmetry::symmetry_element::{RotationGroup, SymmetryElement, SymmetryOperation, TRROT};
16use crate::symmetry::symmetry_element_order::ElementOrder;
17use crate::symmetry::symmetry_transformation::{
18 ComplexConjugationTransformable, DefaultTimeReversalTransformable, SpatialUnitaryTransformable,
19 SpinUnitaryTransformable, SymmetryTransformable, TimeReversalTransformable,
20 TransformationError, assemble_sh_rotation_3d_matrices, assemble_spinor_rotation_matrices,
21 permute_array_by_atoms,
22};
23use crate::target::orbital::MolecularOrbital;
24
25impl<'a, T> SpatialUnitaryTransformable for MolecularOrbital<'a, T, SpinConstraint>
33where
34 T: ComplexFloat + LinalgScalar + ScalarOperand + Copy + Lapack,
35 f64: Into<T>,
36{
37 fn transform_spatial_mut(
38 &mut self,
39 rmat: &Array2<f64>,
40 perm: Option<&Permutation<usize>>,
41 ) -> Result<&mut Self, TransformationError> {
42 let tmatss: Vec<Vec<Array2<T>>> = assemble_sh_rotation_3d_matrices(&self.baos, rmat, perm)
56 .map_err(|err| TransformationError(err.to_string()))?
57 .iter()
58 .map(|tmats| {
59 tmats
60 .iter()
61 .map(|tmat| tmat.mapv(|x| x.into()))
62 .collect::<Vec<_>>()
63 })
64 .collect::<Vec<_>>();
65 let component_boundary_indices = self
66 .baos
67 .iter()
68 .scan(0, |acc, bao| {
69 let start_index = *acc;
70 *acc += bao.n_funcs();
71 Some((start_index, *acc))
72 })
73 .collect::<Vec<_>>();
74 assert_eq!(tmatss.len(), component_boundary_indices.len());
75 assert_eq!(
76 tmatss.len(),
77 self.structure_constraint
78 .n_explicit_comps_per_coefficient_matrix()
79 );
80
81 let old_coeff = &self.coefficients;
82 let new_coefficients = {
83 let t_p_comp_blocks = component_boundary_indices
106 .iter()
107 .zip(self.baos.iter())
108 .zip(tmatss.iter())
109 .map(|(((comp_start, comp_end), bao), tmats)| {
110 let old_coeff_comp: Array1<T> =
111 old_coeff.slice(s![*comp_start..*comp_end]).to_owned();
112 let p_coeff = if let Some(p) = perm {
113 permute_array_by_atoms(&old_coeff_comp, p, &[Axis(0)], bao)
114 } else {
115 old_coeff_comp.clone()
116 };
117 let pbao = if let Some(p) = perm {
118 bao.permute(p)
119 .map_err(|err| TransformationError(err.to_string()))?
120 } else {
121 (*bao).clone()
122 };
123 let t_p_blocks = pbao
124 .shell_boundary_indices()
125 .into_iter()
126 .zip(tmats.iter())
127 .map(|((shl_start, shl_end), tmat)| {
128 tmat.dot(&p_coeff.slice(s![shl_start..shl_end]))
129 })
130 .collect::<Vec<_>>();
131 Ok(t_p_blocks)
132 })
133 .collect::<Result<Vec<Vec<Array1<T>>>, _>>()?
134 .into_iter()
135 .flatten()
136 .collect::<Vec<_>>();
137
138 concatenate(
139 Axis(0),
140 &t_p_comp_blocks
141 .iter()
142 .map(|t_p_block| t_p_block.view())
143 .collect::<Vec<_>>(),
144 )
145 .map_err(|err| {
146 TransformationError(format!(
147 "Unable to concatenate the transformed blocks: {err}."
148 ))
149 })?
150 };
198 self.coefficients = new_coefficients;
199 Ok(self)
200 }
201}
202
203impl<'a> SpinUnitaryTransformable for MolecularOrbital<'a, f64, SpinConstraint> {
212 fn transform_spin_mut(
213 &mut self,
214 dmat: &Array2<Complex<f64>>,
215 ) -> Result<&mut Self, TransformationError> {
216 let cdmat = dmat.view().split_complex();
217 if approx::relative_ne!(
218 cdmat.im.map(|x| x.powi(2)).sum().sqrt(),
219 0.0,
220 epsilon = 1e-14,
221 max_relative = 1e-14,
222 ) {
223 log::error!("Spin transformation matrix is complex-valued:\n{dmat}");
224 Err(TransformationError(
225 "Complex spin transformations cannot be performed with real coefficients."
226 .to_string(),
227 ))
228 } else {
229 let rdmat = cdmat.re.to_owned();
230 match self.structure_constraint {
231 SpinConstraint::Restricted(_) => {
232 if approx::relative_eq!(
233 (&rdmat - Array2::<f64>::eye(2))
234 .map(|x| x.abs().powi(2))
235 .sum()
236 .sqrt(),
237 0.0,
238 epsilon = 1e-14,
239 max_relative = 1e-14,
240 ) {
241 Ok(self)
243 } else if approx::relative_eq!(
244 (&rdmat + Array2::<f64>::eye(2))
245 .map(|x| x.abs().powi(2))
246 .sum()
247 .sqrt(),
248 0.0,
249 epsilon = 1e-14,
250 max_relative = 1e-14,
251 ) {
252 self.coefficients *= -1.0;
254 Ok(self)
255 } else {
256 log::error!("Unsupported spin transformation matrix:\n{}", &rdmat);
257 Err(TransformationError(
258 "Only the identity or negative identity spin transformations are possible with restricted spin constraint."
259 .to_string(),
260 ))
261 }
262 }
263 SpinConstraint::Unrestricted(nspins, increasingm) => {
264 if nspins != 2 {
267 return Err(TransformationError(
268 "Only two-component spinor transformations are supported for now."
269 .to_string(),
270 ));
271 }
272 let dmat_y = array![[0.0, -1.0], [1.0, 0.0]];
273 if approx::relative_eq!(
274 (&rdmat - Array2::<f64>::eye(2))
275 .map(|x| x.abs().powi(2))
276 .sum()
277 .sqrt(),
278 0.0,
279 epsilon = 1e-14,
280 max_relative = 1e-14,
281 ) {
282 Ok(self)
284 } else if approx::relative_eq!(
285 (&rdmat + Array2::<f64>::eye(2))
286 .map(|x| x.abs().powi(2))
287 .sum()
288 .sqrt(),
289 0.0,
290 epsilon = 1e-14,
291 max_relative = 1e-14,
292 ) {
293 self.coefficients *= -1.0;
295 Ok(self)
296 } else if approx::relative_eq!(
297 (&rdmat - &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
298 0.0,
299 epsilon = 1e-14,
300 max_relative = 1e-14,
301 ) {
302 if increasingm {
304 if self.component_index == 0 {
305 self.component_index = 1;
306 self.coefficients *= -1.0;
307 } else {
308 assert_eq!(self.component_index, 1);
309 self.component_index = 0;
310 }
311 } else if self.component_index == 0 {
312 self.component_index = 1;
313 } else {
314 assert_eq!(self.component_index, 1);
315 self.component_index = 0;
316 self.coefficients *= -1.0;
317 }
318 Ok(self)
319 } else if approx::relative_eq!(
320 (&rdmat + &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
321 0.0,
322 epsilon = 1e-14,
323 max_relative = 1e-14,
324 ) {
325 if increasingm {
327 if self.component_index == 0 {
328 self.component_index = 1;
329 } else {
330 assert_eq!(self.component_index, 1);
331 self.component_index = 0;
332 self.coefficients *= -1.0;
333 }
334 } else if self.component_index == 0 {
335 self.component_index = 1;
336 self.coefficients *= -1.0;
337 } else {
338 assert_eq!(self.component_index, 1);
339 self.component_index = 0;
340 }
341 Ok(self)
342 } else {
343 log::error!("Unsupported spin transformation matrix:\n{rdmat}");
344 Err(TransformationError(
345 "Only the identity or πy spin transformations are possible with unrestricted spin constraint."
346 .to_string(),
347 ))
348 }
349 }
350 SpinConstraint::Generalised(nspins, increasingm) => {
351 if nspins != 2 {
352 return Err(TransformationError(
353 "Only two-component spinor transformations are supported for now."
354 .to_string(),
355 ));
356 }
357
358 let nspatial_set = self
359 .baos
360 .iter()
361 .map(|bao| bao.n_funcs())
362 .collect::<HashSet<usize>>();
363 if nspatial_set.len() != 1 {
364 return Err(TransformationError("Both explicit components in the generalised spin constraint must have the same number of spatial AO basis functions.".to_string()));
365 }
366 let nspatial = *nspatial_set.iter().next().ok_or_else(|| {
367 TransformationError(
368 "Unable to obtain the number of spatial AO basis functions."
369 .to_string(),
370 )
371 })?;
372
373 let old_coeff = &self.coefficients;
374 let new_coefficients = if increasingm {
375 let b_coeff = old_coeff.slice(s![0..nspatial]).to_owned();
376 let a_coeff = old_coeff.slice(s![nspatial..2 * nspatial]).to_owned();
377 let t_a_coeff = &a_coeff * rdmat[[0, 0]] + &b_coeff * rdmat[[0, 1]];
378 let t_b_coeff = &a_coeff * rdmat[[1, 0]] + &b_coeff * rdmat[[1, 1]];
379 concatenate(Axis(0), &[t_b_coeff.view(), t_a_coeff.view()]).expect(
380 "Unable to concatenate the transformed rows for the various shells.",
381 )
382 } else {
383 let a_coeff = old_coeff.slice(s![0..nspatial]).to_owned();
384 let b_coeff = old_coeff.slice(s![nspatial..2 * nspatial]).to_owned();
385 let t_a_coeff = &a_coeff * rdmat[[0, 0]] + &b_coeff * rdmat[[0, 1]];
386 let t_b_coeff = &a_coeff * rdmat[[1, 0]] + &b_coeff * rdmat[[1, 1]];
387 concatenate(Axis(0), &[t_a_coeff.view(), t_b_coeff.view()]).expect(
388 "Unable to concatenate the transformed rows for the various shells.",
389 )
390 };
391 self.coefficients = new_coefficients;
392 Ok(self)
393 }
394 }
395 }
396 }
397}
398
399impl<'a, T> SpinUnitaryTransformable for MolecularOrbital<'a, Complex<T>, SpinConstraint>
404where
405 T: Clone,
406 Complex<T>: ComplexFloat<Real = T>
407 + LinalgScalar
408 + ScalarOperand
409 + Lapack
410 + Mul<Complex<T>, Output = Complex<T>>
411 + Mul<Complex<f64>, Output = Complex<T>>,
412{
413 fn transform_spin_mut(
414 &mut self,
415 dmat: &Array2<Complex<f64>>,
416 ) -> Result<&mut Self, TransformationError> {
417 match self.structure_constraint {
418 SpinConstraint::Restricted(_) => {
419 if approx::relative_eq!(
420 (dmat - Array2::<Complex<f64>>::eye(2))
421 .map(|x| x.abs().powi(2))
422 .sum()
423 .sqrt(),
424 0.0,
425 epsilon = 1e-14,
426 max_relative = 1e-14,
427 ) {
428 Ok(self)
430 } else if approx::relative_eq!(
431 (dmat + Array2::<Complex<f64>>::eye(2))
432 .map(|x| x.abs().powi(2))
433 .sum()
434 .sqrt(),
435 0.0,
436 epsilon = 1e-14,
437 max_relative = 1e-14,
438 ) {
439 self.coefficients.map_inplace(|x| *x = -*x);
441 Ok(self)
442 } else {
443 log::error!("Unsupported spin transformation matrix:\n{}", dmat);
444 Err(TransformationError(
445 "Only the identity or negative identity spin transformations are possible with restricted spin constraint."
446 .to_string(),
447 ))
448 }
449 }
450 SpinConstraint::Unrestricted(nspins, increasingm) => {
451 if nspins != 2 {
454 return Err(TransformationError(
455 "Only two-component spinor transformations are supported for now."
456 .to_string(),
457 ));
458 }
459 let dmat_y = array![
460 [Complex::from(0.0), Complex::from(-1.0)],
461 [Complex::from(1.0), Complex::from(0.0)],
462 ];
463 if approx::relative_eq!(
464 (dmat - Array2::<Complex<f64>>::eye(2))
465 .map(|x| x.abs().powi(2))
466 .sum()
467 .sqrt(),
468 0.0,
469 epsilon = 1e-14,
470 max_relative = 1e-14,
471 ) {
472 Ok(self)
474 } else if approx::relative_eq!(
475 (dmat + Array2::<Complex<f64>>::eye(2))
476 .map(|x| x.abs().powi(2))
477 .sum()
478 .sqrt(),
479 0.0,
480 epsilon = 1e-14,
481 max_relative = 1e-14,
482 ) {
483 self.coefficients.map_inplace(|x| *x = -*x);
485 Ok(self)
486 } else if approx::relative_eq!(
487 (dmat - &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
488 0.0,
489 epsilon = 1e-14,
490 max_relative = 1e-14,
491 ) {
492 if increasingm {
494 if self.component_index == 0 {
495 self.component_index = 1;
496 self.coefficients.map_inplace(|x| *x = -*x);
497 } else {
498 assert_eq!(self.component_index, 1);
499 self.component_index = 0;
500 }
501 } else if self.component_index == 0 {
502 self.component_index = 1;
503 } else {
504 assert_eq!(self.component_index, 1);
505 self.component_index = 0;
506 self.coefficients.map_inplace(|x| *x = -*x);
507 }
508 Ok(self)
509 } else if approx::relative_eq!(
510 (dmat + &dmat_y).map(|x| x.abs().powi(2)).sum().sqrt(),
511 0.0,
512 epsilon = 1e-14,
513 max_relative = 1e-14,
514 ) {
515 if increasingm {
517 if self.component_index == 0 {
518 self.component_index = 1;
519 } else {
520 assert_eq!(self.component_index, 1);
521 self.component_index = 0;
522 self.coefficients.map_inplace(|x| *x = -*x);
523 }
524 } else if self.component_index == 0 {
525 self.component_index = 1;
526 self.coefficients.map_inplace(|x| *x = -*x);
527 } else {
528 assert_eq!(self.component_index, 1);
529 self.component_index = 0;
530 }
531 Ok(self)
532 } else {
533 log::error!("Unsupported spin transformation matrix:\n{dmat}");
534 Err(TransformationError(
535 "Only the identity or πy spin transformations are possible with unrestricted spin constraint."
536 .to_string(),
537 ))
538 }
539 }
540 SpinConstraint::Generalised(nspins, increasingm) => {
541 if nspins != 2 {
542 panic!("Only two-component spinor transformations are supported for now.");
543 }
544
545 let nspatial_set = self
546 .baos
547 .iter()
548 .map(|bao| bao.n_funcs())
549 .collect::<HashSet<usize>>();
550 if nspatial_set.len() != 1 {
551 return Err(TransformationError("Both explicit components in the generalised spin constraint must have the same number of spatial AO basis functions.".to_string()));
552 }
553 let nspatial = *nspatial_set.iter().next().ok_or_else(|| {
554 TransformationError(
555 "Unable to obtain the number of spatial AO basis functions.".to_string(),
556 )
557 })?;
558
559 let old_coeff = &self.coefficients;
560 let new_coefficients = if increasingm {
561 let b_coeff = old_coeff.slice(s![0..nspatial]).to_owned();
562 let a_coeff = old_coeff.slice(s![nspatial..2 * nspatial]).to_owned();
563 let t_a_coeff = &a_coeff * dmat[[0, 0]] + &b_coeff * dmat[[0, 1]];
564 let t_b_coeff = &a_coeff * dmat[[1, 0]] + &b_coeff * dmat[[1, 1]];
565 concatenate(Axis(0), &[t_b_coeff.view(), t_a_coeff.view()]).expect(
566 "Unable to concatenate the transformed rows for the various shells.",
567 )
568 } else {
569 let a_coeff = old_coeff.slice(s![0..nspatial]).to_owned();
570 let b_coeff = old_coeff.slice(s![nspatial..2 * nspatial]).to_owned();
571 let t_a_coeff = &a_coeff * dmat[[0, 0]] + &b_coeff * dmat[[0, 1]];
572 let t_b_coeff = &a_coeff * dmat[[1, 0]] + &b_coeff * dmat[[1, 1]];
573 concatenate(Axis(0), &[t_a_coeff.view(), t_b_coeff.view()]).expect(
574 "Unable to concatenate the transformed rows for the various shells.",
575 )
576 };
577 self.coefficients = new_coefficients;
578 Ok(self)
579 }
580 }
581 }
582}
583
584impl<'a, T> SymmetryTransformable for MolecularOrbital<'a, T, SpinConstraint>
588where
589 T: ComplexFloat + Lapack,
590 MolecularOrbital<'a, T, SpinConstraint>:
591 SpatialUnitaryTransformable + SpinUnitaryTransformable + TimeReversalTransformable,
592{
593 fn sym_permute_sites_spatial(
594 &self,
595 symop: &SymmetryOperation,
596 ) -> Result<Permutation<usize>, TransformationError> {
597 symop
598 .act_permute(&self.mol.molecule_ordinary_atoms())
599 .ok_or(TransformationError(format!(
600 "Unable to determine the atom permutation corresponding to the operation `{symop}`."
601 )))
602 }
603}
604
605impl<'a, T> DefaultTimeReversalTransformable for MolecularOrbital<'a, T, SpinConstraint> where
609 T: ComplexFloat + Lapack
610{
611}
612
613impl<'a, T> SpatialUnitaryTransformable for MolecularOrbital<'a, T, SpinOrbitCoupled>
622where
623 T: ComplexFloat + LinalgScalar + ScalarOperand + Copy + Lapack,
624 f64: Into<T>,
625{
626 fn transform_spatial_mut(
627 &mut self,
628 _rmat: &Array2<f64>,
629 _perm: Option<&Permutation<usize>>,
630 ) -> Result<&mut Self, TransformationError> {
631 Err(TransformationError(
632 "Unable to apply only spatial transformations to a spin--orbit-coupled molecular orbital."
633 .to_string(),
634 ))
635 }
636}
637
638impl<'a, T> SpinUnitaryTransformable for MolecularOrbital<'a, T, SpinOrbitCoupled>
643where
644 T: ComplexFloat + LinalgScalar + ScalarOperand + Copy + Lapack,
645 f64: Into<T>,
646{
647 fn transform_spin_mut(
648 &mut self,
649 _dmat: &Array2<Complex<f64>>,
650 ) -> Result<&mut Self, TransformationError> {
651 Err(TransformationError(
652 "Unable to apply only spin transformations to a spin--orbit-coupled molecular orbital."
653 .to_string(),
654 ))
655 }
656}
657
658impl<'a> TimeReversalTransformable for MolecularOrbital<'a, Complex<f64>, SpinOrbitCoupled> {
662 fn transform_timerev_mut(&mut self) -> Result<&mut Self, TransformationError> {
663 let t_element = SymmetryElement::builder()
664 .threshold(1e-12)
665 .proper_order(ElementOrder::Int(1))
666 .proper_power(1)
667 .raw_axis(Vector3::new(0.0, 0.0, 1.0))
668 .kind(TRROT)
669 .rotation_group(RotationGroup::SU2(true))
670 .build()
671 .unwrap();
672 let t = SymmetryOperation::builder()
673 .generating_element(t_element)
674 .power(1)
675 .build()
676 .unwrap();
677 let tmatss: Vec<Vec<Array2<Complex<f64>>>> =
678 assemble_spinor_rotation_matrices(&self.baos, &t, None)
679 .map_err(|err| TransformationError(err.to_string()))?;
680 assert_eq!(
681 tmatss.len(),
682 self.structure_constraint
683 .n_explicit_comps_per_coefficient_matrix()
684 );
685
686 let component_boundary_indices = self
687 .baos
688 .iter()
689 .scan(0, |acc, bao| {
690 let start_index = *acc;
691 *acc += bao.n_funcs();
692 Some((start_index, *acc))
693 })
694 .collect::<Vec<_>>();
695 assert_eq!(tmatss.len(), component_boundary_indices.len());
696
697 let new_coefficients = {
698 let old_coeff = self.coefficients();
699 let t_comp_blocks = component_boundary_indices
700 .iter()
701 .zip(self.baos.iter())
702 .zip(tmatss.iter())
703 .map(|(((comp_start, comp_end), bao), tmats)| {
704 let old_coeff_comp: Array1<_> =
705 old_coeff.slice(s![*comp_start..*comp_end]).to_owned();
706
707 let t_blocks = bao
708 .shell_boundary_indices()
709 .into_iter()
710 .zip(tmats.iter())
711 .map(|((shl_start, shl_end), tmat)| {
712 tmat.dot(&old_coeff_comp.slice(s![shl_start..shl_end]))
713 })
714 .collect::<Vec<_>>();
715 Ok(t_blocks)
716 })
717 .collect::<Result<Vec<Vec<Array1<_>>>, _>>()?
718 .into_iter()
719 .flatten()
720 .collect::<Vec<_>>();
721
722 concatenate(
723 Axis(0),
724 &t_comp_blocks
725 .iter()
726 .map(|t_comp_block| t_comp_block.view())
727 .collect::<Vec<_>>(),
728 )
729 .map_err(|err| {
730 TransformationError(format!(
731 "Unable to concatenate the transformed blocks: {err}."
732 ))
733 })?
734 };
735 self.coefficients = new_coefficients;
736
737 self.transform_cc_mut()?;
738 Ok(self)
739 }
740}
741
742impl<'a> SymmetryTransformable for MolecularOrbital<'a, Complex<f64>, SpinOrbitCoupled> {
746 fn sym_permute_sites_spatial(
750 &self,
751 symop: &SymmetryOperation,
752 ) -> Result<Permutation<usize>, TransformationError> {
753 if (symop.generating_element.threshold().log10() - self.mol.threshold.log10()).abs() >= 3.0
754 {
755 log::warn!(
756 "Symmetry operation threshold ({:.3e}) and molecule threshold ({:.3e}) \
757 differ by more than three orders of magnitudes.",
758 symop.generating_element.threshold(),
759 self.mol.threshold
760 )
761 }
762 symop
763 .act_permute(&self.mol.molecule_ordinary_atoms())
764 .ok_or(TransformationError(format!(
765 "Unable to determine the atom permutation corresponding to the operation `{symop}`.",
766 )))
767 }
768
769 fn sym_transform_spin_spatial_mut(
773 &mut self,
774 symop: &SymmetryOperation,
775 ) -> Result<&mut Self, TransformationError> {
776 let perm = self.sym_permute_sites_spatial(symop)?;
777 let tmatss: Vec<Vec<Array2<Complex<f64>>>> =
778 assemble_spinor_rotation_matrices(&self.baos, symop, Some(&perm))
779 .map_err(|err| TransformationError(err.to_string()))?;
780 let component_boundary_indices = self
781 .baos
782 .iter()
783 .scan(0, |acc, bao| {
784 let start_index = *acc;
785 *acc += bao.n_funcs();
786 Some((start_index, *acc))
787 })
788 .collect::<Vec<_>>();
789 assert_eq!(tmatss.len(), component_boundary_indices.len());
790 assert_eq!(
791 tmatss.len(),
792 self.structure_constraint
793 .n_explicit_comps_per_coefficient_matrix()
794 );
795
796 if symop.contains_time_reversal() {
798 self.transform_cc_mut()?;
803 }
804
805 let new_coefficients = {
806 let old_coeff = self.coefficients();
807 let t_p_comp_blocks = component_boundary_indices
808 .iter()
809 .zip(self.baos.iter())
810 .zip(tmatss.iter())
811 .map(|(((comp_start, comp_end), bao), tmats)| {
812 let old_coeff_comp: Array1<_> =
813 old_coeff.slice(s![*comp_start..*comp_end]).to_owned();
814 let p_coeff = permute_array_by_atoms(&old_coeff_comp, &perm, &[Axis(0)], bao);
815 let pbao = bao
816 .permute(&perm)
817 .map_err(|err| TransformationError(err.to_string()))?;
818 let t_p_blocks = pbao
819 .shell_boundary_indices()
820 .into_iter()
821 .zip(tmats.iter())
822 .map(|((shl_start, shl_end), tmat)| {
823 tmat.dot(&p_coeff.slice(s![shl_start..shl_end]))
824 })
825 .collect::<Vec<_>>();
826 Ok(t_p_blocks)
827 })
828 .collect::<Result<Vec<Vec<Array1<_>>>, _>>()?
829 .into_iter()
830 .flatten()
831 .collect::<Vec<_>>();
832
833 concatenate(
834 Axis(0),
835 &t_p_comp_blocks
836 .iter()
837 .map(|t_p_block| t_p_block.view())
838 .collect::<Vec<_>>(),
839 )
840 .map_err(|err| {
841 TransformationError(format!(
842 "Unable to concatenate the transformed blocks: {err}."
843 ))
844 })?
845 };
846 self.coefficients = new_coefficients;
847
848 Ok(self)
849 }
850}
851
852impl<'a, T, SC> ComplexConjugationTransformable for MolecularOrbital<'a, T, SC>
860where
861 T: ComplexFloat + Lapack,
862 SC: StructureConstraint + Clone,
863{
864 fn transform_cc_mut(&mut self) -> Result<&mut Self, TransformationError> {
865 self.coefficients.mapv_inplace(|x| x.conj());
866 self.complex_conjugated = !self.complex_conjugated;
867 Ok(self)
868 }
869}