irrep 3.1.1__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.1 → irrep-3.2.0}/.github/workflows/tests.yml +1 -7
  2. {irrep-3.1.1 → irrep-3.2.0}/PKG-INFO +1 -1
  3. {irrep-3.1.1 → irrep-3.2.0}/irrep/__init__.py +1 -1
  4. {irrep-3.1.1 → irrep-3.2.0}/irrep/_version.py +3 -3
  5. irrep-3.2.0/irrep/altermagnetic_transformer.py +90 -0
  6. {irrep-3.1.1 → irrep-3.2.0}/irrep/bandstructure.py +5 -5
  7. {irrep-3.1.1 → irrep-3.2.0}/irrep/kpoint_gpaw.py +35 -27
  8. {irrep-3.1.1 → irrep-3.2.0}/irrep/parsers/espresso.py +2 -4
  9. {irrep-3.1.1 → irrep-3.2.0}/irrep/parsers/gpaw.py +1 -2
  10. {irrep-3.1.1 → irrep-3.2.0}/irrep/spacegroup.py +37 -4
  11. {irrep-3.1.1 → irrep-3.2.0}/irrep/symmetry_operation.py +34 -25
  12. irrep-3.2.0/irrep/tests/test_altermagnetic.py +26 -0
  13. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_spacegroup.py +49 -3
  14. {irrep-3.1.1 → irrep-3.2.0}/irrep.egg-info/PKG-INFO +1 -1
  15. {irrep-3.1.1 → irrep-3.2.0}/irrep.egg-info/SOURCES.txt +2 -0
  16. {irrep-3.1.1 → irrep-3.2.0}/irrep.egg-info/scm_file_list.json +5766 -5764
  17. irrep-3.2.0/irrep.egg-info/scm_version.json +8 -0
  18. irrep-3.1.1/irrep.egg-info/scm_version.json +0 -8
  19. {irrep-3.1.1 → irrep-3.2.0}/.github/workflows/python-publish.yml +0 -0
  20. {irrep-3.1.1 → irrep-3.2.0}/.gitignore +0 -0
  21. {irrep-3.1.1 → irrep-3.2.0}/INSTALL +0 -0
  22. {irrep-3.1.1 → irrep-3.2.0}/LICENSE +0 -0
  23. {irrep-3.1.1 → irrep-3.2.0}/MANIFEST.in +0 -0
  24. {irrep-3.1.1 → irrep-3.2.0}/README.md +0 -0
  25. {irrep-3.1.1 → irrep-3.2.0}/irrep/cli.py +0 -0
  26. {irrep-3.1.1 → irrep-3.2.0}/irrep/data/msg_numbers.data +0 -0
  27. {irrep-3.1.1 → irrep-3.2.0}/irrep/ebrs.py +0 -0
  28. {irrep-3.1.1 → irrep-3.2.0}/irrep/gvectors.py +0 -0
  29. {irrep-3.1.1 → irrep-3.2.0}/irrep/kpoint.py +0 -0
  30. {irrep-3.1.1 → irrep-3.2.0}/irrep/or_solutions_obtainer.py +0 -0
  31. {irrep-3.1.1 → irrep-3.2.0}/irrep/parsers/__init__.py +0 -0
  32. {irrep-3.1.1 → irrep-3.2.0}/irrep/parsers/abinit.py +0 -0
  33. {irrep-3.1.1 → irrep-3.2.0}/irrep/parsers/common.py +0 -0
  34. {irrep-3.1.1 → irrep-3.2.0}/irrep/parsers/parse_files_bandstructure.py +0 -0
  35. {irrep-3.1.1 → irrep-3.2.0}/irrep/parsers/vasp.py +0 -0
  36. {irrep-3.1.1 → irrep-3.2.0}/irrep/parsers/wannier90.py +0 -0
  37. {irrep-3.1.1 → irrep-3.2.0}/irrep/spacegroup_irreps.py +0 -0
  38. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/__init__.py +0 -0
  39. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/conftest.py +0 -0
  40. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=False.pkl +0 -0
  41. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=False_irred.pkl +0 -0
  42. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=True.pkl +0 -0
  43. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=True_irred.pkl +0 -0
  44. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/magmom_Fe_qe_dmn.txt +0 -0
  45. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/spacegroup_Te_TR.npz +0 -0
  46. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/spacegroup_Te_TR_product.npz +0 -0
  47. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/spacegroup_Te_noTR.npz +0 -0
  48. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/spacegroup_Te_noTR_product.npz +0 -0
  49. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/symm_matrix_block.npz +0 -0
  50. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/ref_data/symm_matrix_full.npz +0 -0
  51. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_abinit_scalar.py +0 -0
  52. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_abinit_spinor.py +0 -0
  53. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_dmn.py +0 -0
  54. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_ebr_decomposition.py +0 -0
  55. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_espresso_hdf5.py +0 -0
  56. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_espresso_spinor.py +0 -0
  57. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_espresso_spinpol.py +0 -0
  58. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_gpaw.py +0 -0
  59. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_paw.py +0 -0
  60. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_refUC.py +0 -0
  61. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_symmetry_indicators.py +0 -0
  62. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_symsep.py +0 -0
  63. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_vasp_scalar.py +0 -0
  64. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_vasp_spinor.py +0 -0
  65. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_wannier_scalar.py +0 -0
  66. {irrep-3.1.1 → irrep-3.2.0}/irrep/tests/test_wannier_spinor.py +0 -0
  67. {irrep-3.1.1 → irrep-3.2.0}/irrep/utility.py +0 -0
  68. {irrep-3.1.1 → irrep-3.2.0}/irrep.egg-info/dependency_links.txt +0 -0
  69. {irrep-3.1.1 → irrep-3.2.0}/irrep.egg-info/entry_points.txt +0 -0
  70. {irrep-3.1.1 → irrep-3.2.0}/irrep.egg-info/requires.txt +0 -0
  71. {irrep-3.1.1 → irrep-3.2.0}/irrep.egg-info/top_level.txt +0 -0
  72. {irrep-3.1.1 → irrep-3.2.0}/pyproject.toml +0 -0
  73. {irrep-3.1.1 → irrep-3.2.0}/setup.cfg +0 -0
  74. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/POSCAR +0 -0
  75. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/README.md +0 -0
  76. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/WAVECAR +0 -0
  77. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/images/bands.png +0 -0
  78. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/images/checktopomat.png +0 -0
  79. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/inputs_maxK/INCAR +0 -0
  80. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/inputs_maxK/KPOINTS +0 -0
  81. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/inputs_maxK/POSCAR +0 -0
  82. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/inputs_maxK/POTCAR +0 -0
  83. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/inputs_scf/INCAR +0 -0
  84. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/inputs_scf/KPOINTS +0 -0
  85. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/inputs_scf/POSCAR +0 -0
  86. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi/inputs_scf/POTCAR +0 -0
  87. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/README.md +0 -0
  88. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/images/bands.png +0 -0
  89. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/images/checktopomat.png +0 -0
  90. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/Bi.txt +0 -0
  91. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/Se.txt +0 -0
  92. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/bands.in +0 -0
  93. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/nscf_IrRep.in +0 -0
  94. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/pp.bands.in +0 -0
  95. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/inputs/scf.in +0 -0
  96. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/charge-density.dat +0 -0
  97. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/data-file-schema.xml +0 -0
  98. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/paw.txt +0 -0
  99. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc1.dat +0 -0
  100. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc2.dat +0 -0
  101. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc3.dat +0 -0
  102. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc4.dat +0 -0
  103. {irrep-3.1.1 → irrep-3.2.0}/tutorial/Bi2Se3/out/Bi2Se3.xml +0 -0
  104. {irrep-3.1.1 → irrep-3.2.0}/tutorial/SnTe/README.md +0 -0
  105. {irrep-3.1.1 → irrep-3.2.0}/tutorial/SnTe/images/bands.png +0 -0
  106. {irrep-3.1.1 → irrep-3.2.0}/tutorial/SnTe/images/checktopomat.png +0 -0
  107. {irrep-3.1.1 → irrep-3.2.0}/tutorial/SnTe/images/refUC.png +0 -0
  108. {irrep-3.1.1 → irrep-3.2.0}/tutorial/SnTe/inputs/Sn.xml +0 -0
  109. {irrep-3.1.1 → irrep-3.2.0}/tutorial/SnTe/inputs/Te.xml +0 -0
  110. {irrep-3.1.1 → irrep-3.2.0}/tutorial/SnTe/inputs/parameters.in +0 -0
  111. {irrep-3.1.1 → irrep-3.2.0}/tutorial/SnTe/maxK_WFK +0 -0
  112. {irrep-3.1.1 → 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.1
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.1'
22
- __version_tuple__ = version_tuple = (3, 1, 1)
21
+ __version__ = version = '3.2.0'
22
+ __version_tuple__ = version_tuple = (3, 2, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g55e0c10c3'
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:
@@ -683,11 +683,9 @@ class BandStructure:
683
683
  return False
684
684
 
685
685
  kpt_coords = [parser.get_kpt_coord(ik) for ik in kplist]
686
- log_message(f'Input files contain {NK} k-points:\n {kpt_coords}', verbosity, 1)
687
686
  kplist_noskip = [ik for ik in kplist if not check_skip(parser.get_kpt_coord(ik))]
688
687
 
689
688
 
690
-
691
689
  # Set Fermi energy
692
690
  if isinstance(EF, str) and EF.lower() == "auto" or EF is None:
693
691
  if EF_in is None:
@@ -724,8 +722,10 @@ class BandStructure:
724
722
 
725
723
 
726
724
  # To do: create writer of description for this class
727
- log_message((f"Input files contain {NK} k-points and {NBin} bands.\n"
728
- 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)
729
729
  log_message(f"Energy cutoff in WAVECAR : {Ecut0}", verbosity, 1)
730
730
  log_message(f"Energy cutoff reduced to : {Ecut}", verbosity, 1)
731
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
@@ -28,8 +28,7 @@ class ParserEspresso(ParserCommon):
28
28
  self.recip_lattice_alat_inv = np.linalg.inv(recip_lattice_alat)
29
29
 
30
30
  def parse_header(self, spin_channel=None):
31
- Ecut0 = float(self.input.find("basis").find("ecutwfc").text)
32
- Ecut0 *= Hartree_eV
31
+ Ecut0 = float(self.input.find("basis").find("ecutwfc").text) * Hartree_eV
33
32
  NK = len(self.bandstr.findall("ks_energies"))
34
33
 
35
34
  self.spin_channel = spin_channel
@@ -123,8 +122,7 @@ class ParserEspresso(ParserCommon):
123
122
  NBin = self.NBin_list[0]
124
123
 
125
124
  if getE:
126
- Energy = np.array(kptxml.find("eigenvalues").text.split(), dtype=float)[NB_skip: NB_skip + NBin]
127
- Energy *= Hartree_eV
125
+ Energy = np.array(kptxml.find("eigenvalues").text.split(), dtype=float)[NB_skip: NB_skip + NBin] * Hartree_eV
128
126
  else:
129
127
  Energy = None
130
128
  npw = int(kptxml.find("npw").text)
@@ -158,11 +158,10 @@ def get_soc_gpaw(calc, ik, flatten=True, theta=0, phi=0):
158
158
  P2_mi = calc.wfs.kpt_qs[q][0].P_ani[a]
159
159
 
160
160
  h_soc[s1, s2] += np.dot(np.dot(P1_mi.conj(), h_ii), P2_mi.T)
161
- h_soc[:] *= Hartree
162
161
  if flatten:
163
162
  h_soc_old = h_soc.copy()
164
163
  h_soc = np.zeros((2 * m, 2 * m), complex)
165
164
  for s1 in range(2):
166
165
  for s2 in range(2):
167
166
  h_soc[s1::2, s2::2] = h_soc_old[s1, s2]
168
- return h_soc
167
+ return h_soc * Hartree
@@ -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}"
@@ -5,7 +5,8 @@ from irrep.tests.test_dmn import REF_FILES_PATH, TMP_FILES_PATH
5
5
 
6
6
  from .conftest import REF_DATA_PATH, TMP_DATA_PATH
7
7
  from irrep.utility import group_numbers
8
-
8
+ import sympy
9
+ from sympy.parsing.sympy_parser import parse_expr, standard_transformations, implicit_multiplication_application
9
10
 
10
11
  a = 4.456
11
12
  c = 5.926
@@ -128,5 +129,50 @@ def test_wyckoff_positions():
128
129
  return
129
130
 
130
131
 
131
- test_conventional_wyckoff_positions()
132
- test_wyckoff_positions()
132
+ def test_wyckoff_positions_2():
133
+ """ Test for MnTe, which has a spacegroup containing WP of the form (2x,...,...), which can break the sympify parser """
134
+ a = 4.17349018
135
+
136
+ cell = (
137
+ np.array([
138
+ [a, 0, 0],
139
+ [-a / 2, np.sqrt(3) * a / 2, 0],
140
+ [0, 0, 6.75345133],
141
+ ]),
142
+
143
+ np.array([
144
+ [0, 0, 0],
145
+ [0, 0, 0.5],
146
+ [1 / 3, 2 / 3, 1 / 4],
147
+ [2 / 3, 1 / 3, 3 / 4],
148
+ ]),
149
+
150
+ np.array([25, 25, 52, 52]),
151
+ )
152
+
153
+ MnTe_wyckoffs = SpaceGroup.wyckoff_positions(cell)
154
+
155
+ first_MnTe_wyckoffs = [
156
+ [1 / 3, 2 / 3, 0.75],
157
+ [1 / 3, 2 / 3, 0.25],
158
+ [0, 0, 0.25],
159
+ [0, 0, 0],
160
+ ]
161
+ print(MnTe_wyckoffs)
162
+ # Testing the first 4 wp because they are numerical arrays not symbols
163
+ for i, wp in enumerate(MnTe_wyckoffs[-4:]):
164
+ numerical_wp = wp.split(',')
165
+ numerical_wp = [ float(x) for x in numerical_wp]
166
+ assert np.allclose(numerical_wp, first_MnTe_wyckoffs[i])
167
+ # checking correct parsing of the symbolic strings
168
+ assert MnTe_wyckoffs[4].split(',')[1] == '2.0*x'
169
+ return
170
+
171
+
172
+ def test_implicit_multiplication_parsing():
173
+ """test lines 1060-1061 of spacegroup.py, which use parse_expr with implicit multiplication"""
174
+ x, y = sympy.symbols('x y')
175
+ _TRANSFORMS = standard_transformations + (implicit_multiplication_application,)
176
+ assert parse_expr('2x', transformations=_TRANSFORMS) == 2 * x
177
+ assert parse_expr('-2x+1', transformations=_TRANSFORMS) == -2 * x + 1
178
+ assert parse_expr('2x-y', transformations=_TRANSFORMS) == 2 * x - y
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: irrep
3
- Version: 3.1.1
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)
@@ -10,6 +10,7 @@ uploadpypi.sh
10
10
  .github/workflows/tests.yml
11
11
  irrep/__init__.py
12
12
  irrep/_version.py
13
+ irrep/altermagnetic_transformer.py
13
14
  irrep/bandstructure.py
14
15
  irrep/cli.py
15
16
  irrep/ebrs.py
@@ -42,6 +43,7 @@ irrep/tests/__init__.py
42
43
  irrep/tests/conftest.py
43
44
  irrep/tests/test_abinit_scalar.py
44
45
  irrep/tests/test_abinit_spinor.py
46
+ irrep/tests/test_altermagnetic.py
45
47
  irrep/tests/test_dmn.py
46
48
  irrep/tests/test_ebr_decomposition.py
47
49
  irrep/tests/test_espresso_hdf5.py