irrep 3.1.0__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. {irrep-3.1.0 → irrep-3.2.0}/.github/workflows/tests.yml +1 -7
  2. {irrep-3.1.0 → irrep-3.2.0}/PKG-INFO +1 -1
  3. {irrep-3.1.0 → irrep-3.2.0}/irrep/__init__.py +1 -1
  4. {irrep-3.1.0 → irrep-3.2.0}/irrep/_version.py +3 -3
  5. irrep-3.2.0/irrep/altermagnetic_transformer.py +90 -0
  6. {irrep-3.1.0 → irrep-3.2.0}/irrep/bandstructure.py +6 -4
  7. {irrep-3.1.0 → irrep-3.2.0}/irrep/kpoint_gpaw.py +35 -27
  8. {irrep-3.1.0 → irrep-3.2.0}/irrep/parsers/espresso.py +10 -5
  9. {irrep-3.1.0 → irrep-3.2.0}/irrep/parsers/gpaw.py +2 -2
  10. {irrep-3.1.0 → irrep-3.2.0}/irrep/parsers/vasp.py +1 -0
  11. {irrep-3.1.0 → irrep-3.2.0}/irrep/spacegroup.py +37 -4
  12. {irrep-3.1.0 → irrep-3.2.0}/irrep/symmetry_operation.py +34 -25
  13. irrep-3.2.0/irrep/tests/test_altermagnetic.py +26 -0
  14. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_gpaw.py +3 -2
  15. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_spacegroup.py +49 -3
  16. {irrep-3.1.0 → irrep-3.2.0}/irrep.egg-info/PKG-INFO +1 -1
  17. {irrep-3.1.0 → irrep-3.2.0}/irrep.egg-info/SOURCES.txt +2 -0
  18. {irrep-3.1.0 → irrep-3.2.0}/irrep.egg-info/scm_file_list.json +5766 -5764
  19. irrep-3.2.0/irrep.egg-info/scm_version.json +8 -0
  20. irrep-3.1.0/irrep.egg-info/scm_version.json +0 -8
  21. {irrep-3.1.0 → irrep-3.2.0}/.github/workflows/python-publish.yml +0 -0
  22. {irrep-3.1.0 → irrep-3.2.0}/.gitignore +0 -0
  23. {irrep-3.1.0 → irrep-3.2.0}/INSTALL +0 -0
  24. {irrep-3.1.0 → irrep-3.2.0}/LICENSE +0 -0
  25. {irrep-3.1.0 → irrep-3.2.0}/MANIFEST.in +0 -0
  26. {irrep-3.1.0 → irrep-3.2.0}/README.md +0 -0
  27. {irrep-3.1.0 → irrep-3.2.0}/irrep/cli.py +0 -0
  28. {irrep-3.1.0 → irrep-3.2.0}/irrep/data/msg_numbers.data +0 -0
  29. {irrep-3.1.0 → irrep-3.2.0}/irrep/ebrs.py +0 -0
  30. {irrep-3.1.0 → irrep-3.2.0}/irrep/gvectors.py +0 -0
  31. {irrep-3.1.0 → irrep-3.2.0}/irrep/kpoint.py +0 -0
  32. {irrep-3.1.0 → irrep-3.2.0}/irrep/or_solutions_obtainer.py +0 -0
  33. {irrep-3.1.0 → irrep-3.2.0}/irrep/parsers/__init__.py +0 -0
  34. {irrep-3.1.0 → irrep-3.2.0}/irrep/parsers/abinit.py +0 -0
  35. {irrep-3.1.0 → irrep-3.2.0}/irrep/parsers/common.py +0 -0
  36. {irrep-3.1.0 → irrep-3.2.0}/irrep/parsers/parse_files_bandstructure.py +0 -0
  37. {irrep-3.1.0 → irrep-3.2.0}/irrep/parsers/wannier90.py +0 -0
  38. {irrep-3.1.0 → irrep-3.2.0}/irrep/spacegroup_irreps.py +0 -0
  39. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/__init__.py +0 -0
  40. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/conftest.py +0 -0
  41. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=False.pkl +0 -0
  42. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=False_irred.pkl +0 -0
  43. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=True.pkl +0 -0
  44. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=True_irred.pkl +0 -0
  45. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/magmom_Fe_qe_dmn.txt +0 -0
  46. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/spacegroup_Te_TR.npz +0 -0
  47. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/spacegroup_Te_TR_product.npz +0 -0
  48. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/spacegroup_Te_noTR.npz +0 -0
  49. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/spacegroup_Te_noTR_product.npz +0 -0
  50. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/symm_matrix_block.npz +0 -0
  51. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/ref_data/symm_matrix_full.npz +0 -0
  52. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_abinit_scalar.py +0 -0
  53. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_abinit_spinor.py +0 -0
  54. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_dmn.py +0 -0
  55. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_ebr_decomposition.py +0 -0
  56. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_espresso_hdf5.py +0 -0
  57. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_espresso_spinor.py +0 -0
  58. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_espresso_spinpol.py +0 -0
  59. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_paw.py +0 -0
  60. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_refUC.py +0 -0
  61. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_symmetry_indicators.py +0 -0
  62. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_symsep.py +0 -0
  63. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_vasp_scalar.py +0 -0
  64. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_vasp_spinor.py +0 -0
  65. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_wannier_scalar.py +0 -0
  66. {irrep-3.1.0 → irrep-3.2.0}/irrep/tests/test_wannier_spinor.py +0 -0
  67. {irrep-3.1.0 → irrep-3.2.0}/irrep/utility.py +0 -0
  68. {irrep-3.1.0 → irrep-3.2.0}/irrep.egg-info/dependency_links.txt +0 -0
  69. {irrep-3.1.0 → irrep-3.2.0}/irrep.egg-info/entry_points.txt +0 -0
  70. {irrep-3.1.0 → irrep-3.2.0}/irrep.egg-info/requires.txt +0 -0
  71. {irrep-3.1.0 → irrep-3.2.0}/irrep.egg-info/top_level.txt +0 -0
  72. {irrep-3.1.0 → irrep-3.2.0}/pyproject.toml +0 -0
  73. {irrep-3.1.0 → irrep-3.2.0}/setup.cfg +0 -0
  74. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/POSCAR +0 -0
  75. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/README.md +0 -0
  76. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/WAVECAR +0 -0
  77. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/images/bands.png +0 -0
  78. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/images/checktopomat.png +0 -0
  79. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/inputs_maxK/INCAR +0 -0
  80. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/inputs_maxK/KPOINTS +0 -0
  81. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/inputs_maxK/POSCAR +0 -0
  82. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/inputs_maxK/POTCAR +0 -0
  83. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/inputs_scf/INCAR +0 -0
  84. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/inputs_scf/KPOINTS +0 -0
  85. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/inputs_scf/POSCAR +0 -0
  86. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi/inputs_scf/POTCAR +0 -0
  87. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/README.md +0 -0
  88. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/images/bands.png +0 -0
  89. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/images/checktopomat.png +0 -0
  90. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/Bi.txt +0 -0
  91. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/Se.txt +0 -0
  92. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/bands.in +0 -0
  93. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/nscf_IrRep.in +0 -0
  94. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/pp.bands.in +0 -0
  95. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/scf.in +0 -0
  96. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/charge-density.dat +0 -0
  97. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/data-file-schema.xml +0 -0
  98. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/paw.txt +0 -0
  99. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc1.dat +0 -0
  100. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc2.dat +0 -0
  101. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc3.dat +0 -0
  102. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc4.dat +0 -0
  103. {irrep-3.1.0 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.xml +0 -0
  104. {irrep-3.1.0 → irrep-3.2.0}/tutorial/SnTe/README.md +0 -0
  105. {irrep-3.1.0 → irrep-3.2.0}/tutorial/SnTe/images/bands.png +0 -0
  106. {irrep-3.1.0 → irrep-3.2.0}/tutorial/SnTe/images/checktopomat.png +0 -0
  107. {irrep-3.1.0 → irrep-3.2.0}/tutorial/SnTe/images/refUC.png +0 -0
  108. {irrep-3.1.0 → irrep-3.2.0}/tutorial/SnTe/inputs/Sn.xml +0 -0
  109. {irrep-3.1.0 → irrep-3.2.0}/tutorial/SnTe/inputs/Te.xml +0 -0
  110. {irrep-3.1.0 → irrep-3.2.0}/tutorial/SnTe/inputs/parameters.in +0 -0
  111. {irrep-3.1.0 → irrep-3.2.0}/tutorial/SnTe/maxK_WFK +0 -0
  112. {irrep-3.1.0 → irrep-3.2.0}/uploadpypi.sh +0 -0
@@ -37,12 +37,6 @@ jobs:
37
37
  - name: Install irrep and dependencies
38
38
  run: |
39
39
  pip install -e .
40
- - name: Set PYTHONPATH
41
- run: echo "PYTHONPATH=$(pwd):$(pwd)/irreptables" >> $GITHUB_ENV
42
- - name: Run tests
43
- run: pytest
44
- env:
45
- PYTHONPATH: ${{ env.PYTHONPATH }}
46
40
  - name: pytest
47
41
  run: |
48
- pytest --color=yes
42
+ pytest
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: irrep
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: A tool to get symmetry proberties of ab-initio wavefunctions, irreduible representations and more.
5
5
  Author-email: "Stepan S. Tsirkin" <stepan.tsirkin@ehu.eus>
6
6
  License: GNU General Public License v3 (GPLv3)
@@ -2,4 +2,4 @@ try:
2
2
  from ._version import version as __version__
3
3
  except ImportError:
4
4
  __version__ = "unknown"
5
- from .parsers.parse_files_bandstructure import parse_files as parse_bandstructure
5
+ from .parsers.parse_files_bandstructure import parse_files as parse_bandstructure # noqa: F401
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '3.1.0'
22
- __version_tuple__ = version_tuple = (3, 1, 0)
21
+ __version__ = version = '3.2.0'
22
+ __version_tuple__ = version_tuple = (3, 2, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g166f0cbd6'
24
+ __commit_id__ = commit_id = 'g5a82e29d3'
@@ -0,0 +1,90 @@
1
+ import numpy as np
2
+
3
+ from .spacegroup import SpaceGroup
4
+ from .utility import all_close_mod1
5
+ from .symmetry_operation import SymmetryOperation, get_atom_map
6
+
7
+
8
+ class AltermagneticTransformer:
9
+
10
+ def __init__(self, calculator,
11
+ symmetrizer_up,
12
+ alter_symop=None,
13
+ rotation_latt=None,
14
+ translation_latt=None,
15
+ ):
16
+ spacegroup_up = symmetrizer_up.spacegroup
17
+ if alter_symop is None:
18
+ alter_symop = SymmetryOperation(rot=rotation_latt,
19
+ trans=translation_latt,
20
+ Lattice=spacegroup_up.Lattice,
21
+ spinor=False
22
+ )
23
+ self.alter_symop = alter_symop
24
+ self.alter_symop.set_gpaw(calculator)
25
+ # self.symops = [symop * alter_symop for symop in spacegroup_up.symmetries]
26
+ self.alter_map = [] # alter_map[i] = j means ki = alter_map_symops[i] * kj
27
+ self.alter_map_isym = []
28
+ self.symmetry_operations = []
29
+ self.k_intermediate = []
30
+ for kirr in symmetrizer_up.kptirr:
31
+ kpt_red = symmetrizer_up.kpoints_all[kirr]
32
+ kpt_red_before_transform = alter_symop.transform_k(kpt_red, inverse=True)
33
+ for ik, kpt in enumerate(symmetrizer_up.kpoints_all):
34
+ if all_close_mod1(kpt, kpt_red_before_transform):
35
+ self.alter_map.append(symmetrizer_up.kpt2kptirr[ik])
36
+ isym = symmetrizer_up.kpt2kptirr_sym[ik]
37
+ self.alter_map_isym.append(isym)
38
+ self.symmetry_operations.append(symmetrizer_up.spacegroup.symmetries[isym])
39
+ self.k_intermediate.append(kpt)
40
+ break
41
+ else:
42
+ raise RuntimeError(f"No corresponding k-point found for R({kpt_red})={kpt_red_before_transform} out of \n{symmetrizer_up.kpoints_all} under the altermagnetic symmetry operation.")
43
+
44
+ @classmethod
45
+ def from_gpaw(cls, calculator, symmetrizer_up, nskip_symmetries=0):
46
+ symop = find_altermagnetic_symmetry(lattice=calculator.atoms.cell,
47
+ positions=calculator.atoms.get_scaled_positions(),
48
+ typat=calculator.atoms.get_atomic_numbers(),
49
+ magmom=calculator.get_magnetic_moments(),
50
+ nskip=nskip_symmetries)
51
+ return cls(calculator, symmetrizer_up, rotation_latt=symop.rotation, translation_latt=symop.translation)
52
+
53
+ def get_kpoint_down(self, ikirr, kpoints_up):
54
+ ik_origin = self.alter_map[ikirr]
55
+ KPtransformed = kpoints_up[ik_origin].get_transformed_copy(
56
+ symmetry_operation=self.symmetry_operations[ikirr],
57
+ k_new=self.k_intermediate[ikirr])
58
+ KPtransformed = KPtransformed.get_transformed_copy(
59
+ symmetry_operation=self.alter_symop,
60
+ k_new=kpoints_up[ikirr].k)
61
+ # TODO : unify the transformations, using just their product
62
+ return KPtransformed
63
+
64
+
65
+ def find_altermagnetic_symmetry(lattice, positions, typat, magmom, nskip=0):
66
+ magmom = np.array(magmom)
67
+ assert magmom.ndim == 1, "Magnetic moments must be a 1D array."
68
+ positions = np.array(positions)
69
+ assert positions.ndim == 2 and positions.shape[1] == 3, "Positions must be a 2D array with shape (N, 3)."
70
+ typat = np.array(typat)
71
+ assert typat.ndim == 1, "Atom types must be a 1D array."
72
+ assert len(positions) == len(typat)
73
+ assert len(positions) == len(magmom), "Number of positions, atom types, and magnetic moments must match."
74
+
75
+ spacegroup_nomag = SpaceGroup.from_cell(real_lattice=lattice, positions=positions, typat=typat, include_TR=False)
76
+ if nskip >= len(spacegroup_nomag.symmetries) // 2:
77
+ raise ValueError(f"nskip={nskip} is too large for the number of symmetries={len(spacegroup_nomag.symmetries)}.")
78
+ cnt = 0
79
+ for symop in spacegroup_nomag.symmetries:
80
+ atommap, T = get_atom_map(symop, positions)
81
+ # print (f"Checking symmetry operation: {symop}, {atommap=}, {T=}")
82
+ assert np.all(typat[atommap] == typat), "Atom types must match under symmetry operation."
83
+ magmom_transformed = magmom[atommap]
84
+ print(f"{magmom_transformed=}, {atommap=},")
85
+ if np.allclose(magmom_transformed, -magmom):
86
+ cnt += 1
87
+ if cnt > nskip:
88
+ return symop
89
+ else:
90
+ raise ValueError("No altermagnetic symmetry operation found for the given magnetic moments.")
@@ -544,7 +544,7 @@ class BandStructure:
544
544
  if read_paw:
545
545
  spacegroup.set_gpaw(calculator=parser.calculator)
546
546
  from irrep.kpoint_gpaw import OverlapPAW
547
- overlap_paw = OverlapPAW(wfs=parser.calculator.wfs)
547
+ overlap_paw = OverlapPAW(calc=parser.calculator)
548
548
 
549
549
  NBin, kpred, Lattice, _spinor, typat, positions, EF_in = parser.parse_header()
550
550
  if Ecut is None:
@@ -682,10 +682,10 @@ class BandStructure:
682
682
  return True
683
683
  return False
684
684
 
685
+ kpt_coords = [parser.get_kpt_coord(ik) for ik in kplist]
685
686
  kplist_noskip = [ik for ik in kplist if not check_skip(parser.get_kpt_coord(ik))]
686
687
 
687
688
 
688
-
689
689
  # Set Fermi energy
690
690
  if isinstance(EF, str) and EF.lower() == "auto" or EF is None:
691
691
  if EF_in is None:
@@ -722,8 +722,10 @@ class BandStructure:
722
722
 
723
723
 
724
724
  # To do: create writer of description for this class
725
- log_message((f"Input files contain {NK} k-points and {NBin} bands.\n"
726
- f"Saving {NBout} bands starting from {IBstart + 1} in the output"), verbosity, 1)
725
+ log_message(f"Wave function file contains total {NK} k-points and {NBin} bands.", verbosity, 1)
726
+ log_message(f"Saving {len(kpt_coords)} k-points and {NBout} bands in the output.", verbosity, 1)
727
+ log_message(f'Saving k-points with coordinates: \n {kpt_coords}', verbosity, 2)
728
+ log_message(f'Saving bands with band indices: {IBstart + 1} to {IBend + 1}', verbosity, 2)
727
729
  log_message(f"Energy cutoff in WAVECAR : {Ecut0}", verbosity, 1)
728
730
  log_message(f"Energy cutoff reduced to : {Ecut}", verbosity, 1)
729
731
 
@@ -1,4 +1,3 @@
1
- from gpaw.utilities.partition import AtomPartition
2
1
  import numpy as np
3
2
  from .kpoint import KpointAbstract
4
3
  from .utility import cached_einsum
@@ -24,11 +23,10 @@ class KpointGPAW(KpointAbstract):
24
23
  def __init__(self, kpt, wavefunction=None, proj=None, nbands=None,
25
24
  RecLattice=None,
26
25
  atom_positions=None):
27
- super().__init__(kpt=kpt, num_bands=nbands)
26
+ super().__init__(kpt=kpt, num_bands=nbands, RecLattice=RecLattice)
28
27
  self.wavefunction = wavefunction
29
28
  self.proj = proj
30
29
  self.nbands = nbands
31
- self.RecLattice = RecLattice
32
30
  self.atom_positions = atom_positions
33
31
 
34
32
  @classmethod
@@ -39,8 +37,8 @@ class KpointGPAW(KpointAbstract):
39
37
  kpt = calc.wfs.kpt_qs[ibz_index][ispin]
40
38
  k = calc.get_ibz_k_points()[ibz_index]
41
39
  nbands = IBend - IBstart
42
- proj = new_paw_projection(kpt.projections, nbands=nbands)
43
- proj.array[:] = kpt.projections.array[IBstart:IBend]
40
+ proj = np.hstack([kpt.P_ani[a][IBstart:IBend] for a in range(len(calc.wfs.setups))])
41
+
44
42
  # Get projections in ibz
45
43
  if RecLattice is None:
46
44
  RecLattice = calc.wfs.gd.reciprocal_lattice
@@ -48,7 +46,10 @@ class KpointGPAW(KpointAbstract):
48
46
 
49
47
  wavefunction = np.array([calc.wfs.get_wave_function_array(n, ibz_index, ispin, periodic=True)
50
48
  for n in range(IBstart, IBend)])
51
- return KpointGPAW(kpt=k, wavefunction=wavefunction, proj=proj, nbands=nbands, RecLattice=RecLattice, atom_positions=atom_positions)
49
+ return KpointGPAW(kpt=k, wavefunction=wavefunction, proj=proj, nbands=nbands,
50
+ RecLattice=RecLattice,
51
+ atom_positions=atom_positions)
52
+
52
53
 
53
54
 
54
55
  def get_transformed_copy(self, symmetry_operation, k_new):
@@ -69,15 +70,27 @@ class KpointGPAW(KpointAbstract):
69
70
 
70
71
  class OverlapPAW:
71
72
 
72
- def __init__(self, wfs):
73
+ def __init__(self, calc, get_soc=True):
74
+
73
75
  self.dO_aii = {}
74
- for a in wfs.kpt_u[0].projections.map:
75
- self.dO_aii[a] = np.copy(wfs.setups[a].dO_ii)
76
- self.dv = np.copy(wfs.gd.dv)
76
+ for a, setup in enumerate(calc.wfs.setups):
77
+ self.dO_aii[a] = np.copy(setup.dO_ii)
78
+ self.orb_atom_indices = np.cumsum([0] + [dO.shape[0] for dO in self.dO_aii.values()])
79
+ self.dv = np.copy(calc.wfs.gd.dv)
80
+ if get_soc:
81
+ from gpaw.spinorbit import soc
82
+ self.dVL_avii = [
83
+ soc(calc.wfs.setups[a], calc.hamiltonian.xc, calc.density.D_asp[a])
84
+ for a in range(len(calc.wfs.setups))]
85
+
77
86
  # print ("Initialized PAW overlap with dO_ii for atoms", list(self.dO_aii.keys()))
78
87
  # print ("dO_ii shapes", {a: dO.shape for a, dO in self.dO_aii.items()})
79
88
  # print ("dv", self.dv)
80
89
 
90
+ def get_orb_indices_on_atom(self, atom_index):
91
+ """Get the orbital indices corresponding to a specific atom index."""
92
+ return self.orb_atom_indices[[atom_index, atom_index + 1]]
93
+
81
94
  def product(self, KP1, KP2,
82
95
  include_paw=True,
83
96
  include_pseudo=True,
@@ -101,23 +114,18 @@ class OverlapPAW:
101
114
  else:
102
115
  phases = np.exp(-2j * np.pi * (positions @ bk))
103
116
  for a, dO_ii in self.dO_aii.items():
104
- prod += (proj1[a].conj() @ dO_ii @ (proj2[a].T)) * phases[a]
117
+ I1, I2 = self.get_orb_indices_on_atom(a)
118
+ prod += (proj1[:, I1:I2].conj() @ dO_ii @ (proj2[:, I1:I2].T)) * phases[a]
105
119
  return prod
106
120
 
107
121
 
108
-
109
- class AtomPartitionSerial(AtomPartition):
110
- def __init__(self, at_part):
111
- self.comm = None
112
- self.rank_a = None
113
- self.my_indices = at_part.my_indices
114
- self.natoms = at_part.natoms
115
- self.name = "serial"
116
-
117
-
118
- def new_paw_projection(proj,
119
- **kwargs):
120
- ap = AtomPartitionSerial(proj.atom_partition)
121
- new_projection = proj.new(bcomm=None, atom_partition=ap, **kwargs)
122
- new_projection.atom_partition = ap
123
- return new_projection
122
+ def soc(self, KP1, KP2):
123
+ from ase.units import Hartree
124
+ dVsoc = np.zeros((3, KP1.nbands, KP2.nbands), dtype=complex)
125
+ for a, dVL_vii in enumerate(self.dVL_avii):
126
+ I1, I2 = self.get_orb_indices_on_atom(a)
127
+ P1_mi = KP1.proj[:, I1:I2].conj()
128
+ P2_mi = KP2.proj[:, I1:I2].T
129
+ for t in range(3):
130
+ dVsoc[t] += P1_mi @ dVL_vii[t] @ P2_mi
131
+ return dVsoc * Hartree
@@ -23,10 +23,12 @@ class ParserEspresso(ParserCommon):
23
23
  outp = myroot.find("output")
24
24
  self.bandstr = outp.find("band_structure")
25
25
  self.spinor = str2bool(self.bandstr.find("noncolin").text)
26
+ recip_lattice_alat = outp.find("basis_set").find("reciprocal_lattice")
27
+ recip_lattice_alat = np.array([recip_lattice_alat.find(f"b{i}").text.split() for i in range(1, 4)], dtype=float)
28
+ self.recip_lattice_alat_inv = np.linalg.inv(recip_lattice_alat)
26
29
 
27
30
  def parse_header(self, spin_channel=None):
28
- Ecut0 = float(self.input.find("basis").find("ecutwfc").text)
29
- Ecut0 *= Hartree_eV
31
+ Ecut0 = float(self.input.find("basis").find("ecutwfc").text) * Hartree_eV
30
32
  NK = len(self.bandstr.findall("ks_energies"))
31
33
 
32
34
  self.spin_channel = spin_channel
@@ -103,6 +105,7 @@ class ParserEspresso(ParserCommon):
103
105
  def get_kpt_coord(self, ik):
104
106
  kptxml = self.bandstr.findall("ks_energies")[ik]
105
107
  kpt = np.array(kptxml.find("k_point").text.split(), dtype=float)
108
+ kpt = kpt.dot(self.recip_lattice_alat_inv)
106
109
  return kpt
107
110
 
108
111
  def parse_kpoint(self, ik, verbosity=0, getE=True, getWF=True):
@@ -119,8 +122,7 @@ class ParserEspresso(ParserCommon):
119
122
  NBin = self.NBin_list[0]
120
123
 
121
124
  if getE:
122
- Energy = np.array(kptxml.find("eigenvalues").text.split(), dtype=float)[NB_skip: NB_skip + NBin]
123
- Energy *= Hartree_eV
125
+ Energy = np.array(kptxml.find("eigenvalues").text.split(), dtype=float)[NB_skip: NB_skip + NBin] * Hartree_eV
124
126
  else:
125
127
  Energy = None
126
128
  npw = int(kptxml.find("npw").text)
@@ -133,6 +135,7 @@ class ParserEspresso(ParserCommon):
133
135
  checked_files = []
134
136
  for extension in ["hdf5", "dat"]:
135
137
  for strcase in [str.lower, str.upper]:
138
+ print(f"{ik=}, {wfcname=}, {extension=}, {strcase=}")
136
139
  filename = f"{self.prefix}.save/{strcase(wfcname)}.{extension}"
137
140
  if os.path.exists(filename):
138
141
  if extension == "hdf5":
@@ -152,7 +155,8 @@ class ParserEspresso(ParserCommon):
152
155
  for atr in attrnames:
153
156
  attributes.append(fWFC.attrs[atr])
154
157
 
155
- _gamma_only, _igwx, ik, _ispin, _nbnd, _ngw, _npol, _scale_factor, xk = attributes
158
+ _gamma_only, _igwx, ik_read, _ispin, _nbnd, _ngw, _npol, _scale_factor, xk = attributes
159
+ assert ik_read == ik + 1, f"Found ik={ik_read} in the wavefunction file {filename}, but expected ik={ik + 1}"
156
160
  kpt = np.array(xk)
157
161
  Miller_Indices = fWFC["MillerIndices"]
158
162
  B = np.array([Miller_Indices.attrs[f"bg{i}"] for i in range(1, 4)])
@@ -178,6 +182,7 @@ class ParserEspresso(ParserCommon):
178
182
  for ib in range(NBin):
179
183
  rec = fWFC.read_record(f"{npwtot * 2}f8")
180
184
  WF[ib] = rec[0::2] + 1.0j * rec[1::2]
185
+ assert np.allclose(kpt, self.get_kpt_coord(ik)), f"Found kpt[{ik}] {kpt} in the wavefunction file {filename}, but expected {self.get_kpt_coord(ik)}"
181
186
  WF = WF.reshape((NBin, npw, nspinor), order="F")
182
187
  return WF, Energy, kg, kpt
183
188
  checked_files.append(filename)
@@ -63,6 +63,7 @@ class ParserGPAW(ParserCommon):
63
63
  log_message(f"shapes of WFupdw: {[wf.shape for wf in WFupdw]}", self.verbosity, 2)
64
64
  ngx, ngy, ngz = WFupdw[0].shape[1:]
65
65
  kpt = self.calculator.get_ibz_k_points()[ik]
66
+ assert np.allclose(kpt, self.get_kpt_coord(ik)), f"Found kpt[{ik}] {kpt} in the wavefunction file, but expected {self.get_kpt_coord(ik)}"
66
67
  kg, eKG = calc_gvectors(
67
68
  kpt,
68
69
  RecLattice,
@@ -157,11 +158,10 @@ def get_soc_gpaw(calc, ik, flatten=True, theta=0, phi=0):
157
158
  P2_mi = calc.wfs.kpt_qs[q][0].P_ani[a]
158
159
 
159
160
  h_soc[s1, s2] += np.dot(np.dot(P1_mi.conj(), h_ii), P2_mi.T)
160
- h_soc[:] *= Hartree
161
161
  if flatten:
162
162
  h_soc_old = h_soc.copy()
163
163
  h_soc = np.zeros((2 * m, 2 * m), complex)
164
164
  for s1 in range(2):
165
165
  for s2 in range(2):
166
166
  h_soc[s1::2, s2::2] = h_soc_old[s1, s2]
167
- return h_soc
167
+ return h_soc * Hartree
@@ -134,6 +134,7 @@ class ParserVasp(ParserCommon):
134
134
  assert npw % 2 == 0, f"odd number of coefs {npw} for spinor wavefunctions"
135
135
  npw //= nspinor
136
136
  kpt = r[1:4]
137
+ assert np.allclose(kpt, self.get_kpt_coord(ik)), f"Found kpt[{ik}] {kpt} in the wavefunction file, but expected {self.get_kpt_coord(ik)}"
137
138
  if getE:
138
139
  Energy = np.array(r[4: 4 + NBin * 3]).reshape(NBin, 3)[:, 0]
139
140
  else:
@@ -30,6 +30,8 @@ import os
30
30
 
31
31
  from pyxtal import Group
32
32
  from sympy import Matrix, sympify
33
+ from sympy.parsing.sympy_parser import parse_expr, standard_transformations, implicit_multiplication_application
34
+
33
35
 
34
36
 
35
37
  class SpaceGroup:
@@ -877,6 +879,7 @@ class SpaceGroup:
877
879
  mag_symprec=0.05,
878
880
  typat=None,
879
881
  magmom=None,
882
+ magnetic=True,
880
883
  spinor=None,
881
884
  verbosity=0,
882
885
  **kwargs_cell):
@@ -908,9 +911,12 @@ class SpaceGroup:
908
911
  lattice = calculator.atoms.cell
909
912
  if typat is None:
910
913
  typat = calculator.atoms.get_atomic_numbers()
911
- if magmom is None:
912
- magmom = calculator.get_magnetic_moments()
913
- assert magmom.shape == (len(calculator.atoms),)
914
+ if magnetic:
915
+ if magmom is None:
916
+ magmom = calculator.get_magnetic_moments()
917
+ assert magmom.shape == (len(calculator.atoms),)
918
+ else:
919
+ magmom = np.zeros(len(calculator.atoms), dtype=float)
914
920
  magmom = group_numbers(magmom, precision=mag_symprec)
915
921
  magmom_set = set(magmom)
916
922
  if len(magmom_set) > 1:
@@ -1055,7 +1061,8 @@ class SpaceGroup:
1055
1061
  for sim_vector in conventional_wyckoffs:
1056
1062
 
1057
1063
  # sim_vector is a string on array shape change to numeric or simbolic
1058
- vector = Matrix([sympify(v) for v in sim_vector.split(',')])
1064
+ _TRANSFORMS = standard_transformations + (implicit_multiplication_application,)
1065
+ vector = Matrix([parse_expr(v, transformations=_TRANSFORMS) for v in sim_vector.split(',')])
1059
1066
  wyckoff_new_base = inv_transformation_matrix * (vector - origin_shift)
1060
1067
 
1061
1068
  # Change to string again
@@ -1067,6 +1074,32 @@ class SpaceGroup:
1067
1074
  return wyckoffs_in_cell_base
1068
1075
 
1069
1076
 
1077
+ def get_transformed_group_index(self, symmetry_operation):
1078
+ """
1079
+ calculates the operations S^-1*g*S for a given symmetry operation S and each symmetry operation g in the group. Returns the index of the transformed operation in the group.
1080
+ Also returns the translation difference between the transformed operation and the corresponding operation in the group.
1081
+ """
1082
+ transformed_group_index = []
1083
+ translation_difference = []
1084
+ symmetry_operation_inv = symmetry_operation.inverse(mod1=False)
1085
+ for sym in self.symmetries:
1086
+ transformed_sym = symmetry_operation_inv.multiply_keeptransl(sym).multiply_keeptransl(symmetry_operation)
1087
+ sym.show()
1088
+ transformed_sym.show()
1089
+ for i, sym2 in enumerate(self.symmetries):
1090
+ if transformed_sym.equals(sym2, mod1=True) and transformed_sym.time_reversal == sym2.time_reversal:
1091
+ transl_diff = transformed_sym.translation - sym2.translation
1092
+ transl_diff_int = np.round(transl_diff).astype(int)
1093
+ if np.allclose(transl_diff, transl_diff_int):
1094
+ transformed_group_index.append(i)
1095
+ translation_difference.append(transl_diff_int)
1096
+ break
1097
+ else:
1098
+ raise ValueError(f"Transformed symmetry operation {transformed_sym.str()} not found in the group")
1099
+ return np.array(transformed_group_index), np.array(translation_difference).astype(int)
1100
+
1101
+
1102
+
1070
1103
  def read_sym_file(fname):
1071
1104
  """
1072
1105
  Read symmetry operations from a file.
@@ -91,7 +91,10 @@ class SymmetryOperation():
91
91
  positions=None,
92
92
  translation_mod1=True, spinor_rotation=None):
93
93
  self.ind = ind
94
- self.rotation = rot
94
+ rot_int = np.array(rot.round(), dtype=int)
95
+ if not np.allclose(rot, rot_int, atol=1e-6):
96
+ raise ValueError(f"Rotation matrix is not integer: {rot}")
97
+ self.rotation = rot_int
95
98
  self.positions = positions
96
99
  self.time_reversal = bool(time_reversal)
97
100
  self.real_lattice = Lattice
@@ -111,7 +114,7 @@ class SymmetryOperation():
111
114
  self.spinor_rotation = spinor_rotation
112
115
  self.sign = 1 # May be changed later externally
113
116
 
114
- def inverse(self):
117
+ def inverse(self, mod1=None):
115
118
  """
116
119
  Create the inverse symmetry operation.
117
120
 
@@ -120,7 +123,7 @@ class SymmetryOperation():
120
123
  SymmetryOperation
121
124
  A new instance of the symmetry operation representing the inverse.
122
125
  """
123
- rot_inv = np.linalg.inv(self.rotation)
126
+ rot_inv = self.rotation_inv
124
127
  trans_inv = -rot_inv @ self.translation
125
128
  spinor_rot_inv = self.spinor_rotation.conj().T
126
129
  if self.time_reversal:
@@ -132,7 +135,7 @@ class SymmetryOperation():
132
135
  time_reversal=self.time_reversal,
133
136
  ind=self.ind,
134
137
  spinor=self.spinor,
135
- translation_mod1=self.translation_mod1,
138
+ translation_mod1=self.translation_mod1 if mod1 is None else mod1,
136
139
  spinor_rotation=spinor_rot_inv)
137
140
 
138
141
  def __mul__(self, other):
@@ -216,6 +219,12 @@ class SymmetryOperation():
216
219
  return False
217
220
  return True
218
221
 
222
+ @cached_property
223
+ def rotation_inv(self):
224
+ rotinv = np.linalg.inv(self.rotation)
225
+ rotinv_int = np.array(rotinv.round(), dtype=int)
226
+ assert np.allclose(rotinv, rotinv_int, atol=1e-6), f"Inverse Rotation matrix is not integer: {rotinv}"
227
+ return rotinv_int
219
228
 
220
229
  @property
221
230
  def lattice(self):
@@ -458,18 +467,18 @@ class SymmetryOperation():
458
467
  ]
459
468
  rotstr = [r + r1 for r, r1 in zip(rotstr, rotstr1)]
460
469
 
461
- matrix = np.transpose(np.linalg.inv(self.rotation))
470
+ matrix = self.rotation_inv.T
462
471
  if self.time_reversal:
463
- matrix *= -1
472
+ matrix = -matrix
464
473
  kstring = "gk = [" + ", ".join(
465
474
  [parse_row_transform(r) for r in matrix]
466
475
  ) + "]"
467
476
 
468
477
 
469
478
  if write_ref:
470
- matrix = np.transpose(np.linalg.inv(R))
479
+ matrix = np.linalg.inv(R).T
471
480
  if self.time_reversal:
472
- matrix *= -1
481
+ matrix = -matrix
473
482
  kstring += " | refUC: gk = [" + ", ".join(
474
483
  [parse_row_transform(r) for r in matrix]
475
484
  ) + "]"
@@ -655,7 +664,7 @@ class SymmetryOperation():
655
664
 
656
665
  return d
657
666
 
658
- def transform_r(self, vector, inverse=False):
667
+ def transform_r(self, vector, inverse=False, translation=True):
659
668
  """
660
669
  Transform a real-space vector (in lattice coordinates) under the symmetry operation.
661
670
 
@@ -670,9 +679,15 @@ class SymmetryOperation():
670
679
  Transformed vector.
671
680
  """
672
681
  if inverse:
673
- return (np.array(vector) - self.translation[..., :]).dot(self.rotation_inv.T)
682
+ result = np.array(vector)
683
+ if translation:
684
+ result -= self.translation[..., :]
685
+ return result.dot(self.rotation_inv.T)
674
686
  else:
675
- return np.array(vector).dot(self.rotation.T) + self.translation[..., :]
687
+ result = np.array(vector).dot(self.rotation.T).astype(float)
688
+ if translation:
689
+ result += self.translation[..., :]
690
+ return result
676
691
 
677
692
 
678
693
  @cached_property
@@ -702,14 +717,6 @@ class SymmetryOperation():
702
717
  def det(self):
703
718
  return np.linalg.det(self.rotation)
704
719
 
705
-
706
- @cached_property
707
- def rotation_inv(self):
708
- rotinv = np.linalg.inv(self.rotation)
709
- rotinv_int = np.array(rotinv.round(), dtype=int)
710
- assert np.allclose(rotinv, rotinv_int, atol=1e-6), f"Inverse Rotation matrix is not integer: {rotinv}"
711
- return rotinv_int
712
-
713
720
  @cached_property
714
721
  def spinor_rotation_TR(self):
715
722
  """
@@ -862,6 +869,7 @@ class SymmetryOperation():
862
869
  symmetry = SymmetryGpaw1(self)
863
870
  R_aii = AtomRotations(setups.setups, setups.id_a, symmetry).get_R_asii()
864
871
  self.R_aii = [R_ii[0] for R_ii in R_aii] # remove unnecessary nesting
872
+ self.orb_atom_indices = np.cumsum([0] + [R.shape[0] for R in self.R_aii])
865
873
 
866
874
  # def get_U_aii_gpaw(self, kpoint):
867
875
  # """Phase corrected rotation matrices for the PAW projections."""
@@ -896,16 +904,17 @@ class SymmetryOperation():
896
904
  proj_rot : Projections
897
905
  the rotated projection coefficients
898
906
  """
899
- from .kpoint_gpaw import new_paw_projection
900
- mapped_projections = new_paw_projection(projections)
901
-
907
+ # from .kpoint_gpaw import new_paw_projection
908
+ mapped_projections = np.zeros_like(projections)
902
909
  for a, R_ii in enumerate(self.R_aii):
903
- Pout_ni = (projections[a] @ R_ii.T) # * np.exp(2j * np.pi * k_target @ self.atom_map_T[a])
910
+ b = self.atom_map[a]
911
+ I1, I2 = self.orb_atom_indices[ [a, a + 1]]
912
+ J1, J2 = self.orb_atom_indices[ [b, b + 1]]
913
+ Pout_ni = (projections[:, I1:I2] @ R_ii.T) # * np.exp(2j * np.pi * k_target @ self.atom_map_T[a])
904
914
  if self.time_reversal:
905
915
  Pout_ni = np.conj(Pout_ni)
906
916
  Pout_ni = Pout_ni * np.exp(2j * np.pi * k_target @ self.atom_map_T[a])
907
- I1, I2 = mapped_projections.map[self.atom_map[a]]
908
- mapped_projections.array[..., I1:I2] = Pout_ni
917
+ mapped_projections[:, J1:J2] = Pout_ni
909
918
  return mapped_projections
910
919
 
911
920
  def rotate_pseudo_wavefunction(self, psi_n_grid, k_origin, k_target):
@@ -0,0 +1,26 @@
1
+ import numpy as np
2
+ from pytest import approx
3
+
4
+ from irrep.altermagnetic_transformer import find_altermagnetic_symmetry
5
+
6
+
7
+ def test_find_altermagnetic_symmetry():
8
+
9
+ a = 4.134
10
+ c = 6.652
11
+ lattice = a * np.array([[1, 0, 0], [-1 / 2, np.sqrt(3) / 2, 0], [0, 0, c / a]])
12
+ positions = np.array(
13
+ [
14
+ [0, 0, 0],
15
+ [0, 0, 1 / 2],
16
+ [1 / 3, 2 / 3, 1 / 4],
17
+ [2 / 3, 1 / 3, 3 / 4],
18
+ ]
19
+ )
20
+ magmom = np.array([1, -1, 0, 0])
21
+ symop = find_altermagnetic_symmetry(lattice=lattice, positions=positions, typat=[1, 1, 2, 2], magmom=magmom)
22
+ assert symop.angle == approx(np.pi / 3), f"Expected angle=np.pi/3, got {symop.angle}"
23
+ assert symop.axis == approx([0, 0, 1]), f"Expected axis=[0, 0, 1], got {symop.axis}"
24
+ assert symop.translation == approx([0, 0, 0.5]), f"Expected translation=[0, 0, 0.5], got {symop.translation}"
25
+ assert symop.inversion is False, f"Expected inversion=False, got {symop.inversion}"
26
+ assert symop.time_reversal is False, f"Expected timereversal=False, got {symop.timereversal}"
@@ -3,6 +3,7 @@ import numpy as np
3
3
  import pytest
4
4
  from irrep.bandstructure import BandStructure
5
5
  from pytest import approx
6
+ from .conftest import TEST_FILES_PATH
6
7
 
7
8
 
8
9
 
@@ -10,7 +11,7 @@ from pytest import approx
10
11
 
11
12
  @pytest.mark.parametrize("spinor", [True, False])
12
13
  def test_gpaw_spinorbit(spinor):
13
- calc = GPAW("../../examples/gpaw/Bi-gamma.gpw")
14
+ calc = GPAW(f"{TEST_FILES_PATH}/gpaw/Bi-gamma.gpw")
14
15
 
15
16
  nspinor = 2 if spinor else 1
16
17
 
@@ -26,7 +27,7 @@ def test_gpaw_spinorbit(spinor):
26
27
  read_paw=not spinor, # for now, read_pae does not work with spinor calculations
27
28
  search_cell=True,
28
29
  kplist=[0])
29
- output_file = "../../examples/gpaw/Bi-gamma_bandstructure.pickle"
30
+ output_file = f"{TEST_FILES_PATH}/gpaw/Bi-gamma_bandstructure.pickle"
30
31
  bandstructure.pickle(output_file)
31
32
  # print ("Bandstructure",bandstructure)
32
33
  bandstructure.spacegroup.show()