irrep 3.3.0__tar.gz → 3.4.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 (115) hide show
  1. irrep-3.4.0/.github/workflows/tests.yml +46 -0
  2. {irrep-3.3.0 → irrep-3.4.0}/PKG-INFO +1 -1
  3. {irrep-3.3.0 → irrep-3.4.0}/irrep/_version.py +3 -3
  4. {irrep-3.3.0 → irrep-3.4.0}/irrep/bandstructure.py +3 -1
  5. {irrep-3.3.0 → irrep-3.4.0}/irrep/kpoint.py +0 -2
  6. {irrep-3.3.0 → irrep-3.4.0}/irrep/kpoint_gpaw.py +15 -22
  7. {irrep-3.3.0 → irrep-3.4.0}/irrep/parsers/espresso.py +0 -1
  8. {irrep-3.3.0 → irrep-3.4.0}/irrep/parsers/gpaw.py +83 -7
  9. {irrep-3.3.0 → irrep-3.4.0}/irrep/spacegroup.py +21 -64
  10. {irrep-3.3.0 → irrep-3.4.0}/irrep/symmetry_operation.py +39 -26
  11. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/conftest.py +13 -0
  12. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_gpaw.py +43 -16
  13. irrep-3.4.0/irrep/tests/test_gpaw_vs_legacy.py +46 -0
  14. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_paw.py +39 -13
  15. {irrep-3.3.0 → irrep-3.4.0}/irrep.egg-info/PKG-INFO +1 -1
  16. {irrep-3.3.0 → irrep-3.4.0}/irrep.egg-info/SOURCES.txt +1 -0
  17. {irrep-3.3.0 → irrep-3.4.0}/irrep.egg-info/scm_file_list.json +12 -6
  18. irrep-3.4.0/irrep.egg-info/scm_version.json +8 -0
  19. irrep-3.3.0/.github/workflows/tests.yml +0 -42
  20. irrep-3.3.0/irrep.egg-info/scm_version.json +0 -8
  21. {irrep-3.3.0 → irrep-3.4.0}/.github/workflows/python-publish.yml +0 -0
  22. {irrep-3.3.0 → irrep-3.4.0}/.gitignore +0 -0
  23. {irrep-3.3.0 → irrep-3.4.0}/INSTALL +0 -0
  24. {irrep-3.3.0 → irrep-3.4.0}/LICENSE +0 -0
  25. {irrep-3.3.0 → irrep-3.4.0}/MANIFEST.in +0 -0
  26. {irrep-3.3.0 → irrep-3.4.0}/README.md +0 -0
  27. {irrep-3.3.0 → irrep-3.4.0}/irrep/__init__.py +0 -0
  28. {irrep-3.3.0 → irrep-3.4.0}/irrep/altermagnetic_transformer.py +0 -0
  29. {irrep-3.3.0 → irrep-3.4.0}/irrep/cli.py +0 -0
  30. {irrep-3.3.0 → irrep-3.4.0}/irrep/data/msg_numbers.data +0 -0
  31. {irrep-3.3.0 → irrep-3.4.0}/irrep/ebrs.py +0 -0
  32. {irrep-3.3.0 → irrep-3.4.0}/irrep/gvectors.py +0 -0
  33. {irrep-3.3.0 → irrep-3.4.0}/irrep/or_solutions_obtainer.py +0 -0
  34. {irrep-3.3.0 → irrep-3.4.0}/irrep/parsers/__init__.py +0 -0
  35. {irrep-3.3.0 → irrep-3.4.0}/irrep/parsers/abinit.py +0 -0
  36. {irrep-3.3.0 → irrep-3.4.0}/irrep/parsers/common.py +0 -0
  37. {irrep-3.3.0 → irrep-3.4.0}/irrep/parsers/parse_files_bandstructure.py +0 -0
  38. {irrep-3.3.0 → irrep-3.4.0}/irrep/parsers/vasp.py +0 -0
  39. {irrep-3.3.0 → irrep-3.4.0}/irrep/parsers/wannier90.py +0 -0
  40. {irrep-3.3.0 → irrep-3.4.0}/irrep/spacegroup_irreps.py +0 -0
  41. {irrep-3.3.0 → irrep-3.4.0}/irrep/storage.py +0 -0
  42. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/__init__.py +0 -0
  43. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=False.pkl +0 -0
  44. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=False_irred.pkl +0 -0
  45. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=True.pkl +0 -0
  46. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=True_irred.pkl +0 -0
  47. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/magmom_Fe_qe_dmn.txt +0 -0
  48. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/spacegroup_Te_TR.npz +0 -0
  49. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/spacegroup_Te_TR_product.npz +0 -0
  50. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/spacegroup_Te_noTR.npz +0 -0
  51. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/spacegroup_Te_noTR_product.npz +0 -0
  52. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/symm_matrix_block.npz +0 -0
  53. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/ref_data/symm_matrix_full.npz +0 -0
  54. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_abinit_scalar.py +0 -0
  55. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_abinit_spinor.py +0 -0
  56. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_altermagnetic.py +0 -0
  57. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_dmn.py +0 -0
  58. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_ebr_decomposition.py +0 -0
  59. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_espresso_hdf5.py +0 -0
  60. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_espresso_spinor.py +0 -0
  61. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_espresso_spinpol.py +0 -0
  62. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_refUC.py +0 -0
  63. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_spacegroup.py +0 -0
  64. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_symmetry_indicators.py +0 -0
  65. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_symsep.py +0 -0
  66. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_vasp_scalar.py +0 -0
  67. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_vasp_spinor.py +0 -0
  68. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_wannier_scalar.py +0 -0
  69. {irrep-3.3.0 → irrep-3.4.0}/irrep/tests/test_wannier_spinor.py +0 -0
  70. {irrep-3.3.0 → irrep-3.4.0}/irrep/utility.py +0 -0
  71. {irrep-3.3.0 → irrep-3.4.0}/irrep.egg-info/dependency_links.txt +0 -0
  72. {irrep-3.3.0 → irrep-3.4.0}/irrep.egg-info/entry_points.txt +0 -0
  73. {irrep-3.3.0 → irrep-3.4.0}/irrep.egg-info/requires.txt +0 -0
  74. {irrep-3.3.0 → irrep-3.4.0}/irrep.egg-info/top_level.txt +0 -0
  75. {irrep-3.3.0 → irrep-3.4.0}/pyproject.toml +0 -0
  76. {irrep-3.3.0 → irrep-3.4.0}/setup.cfg +0 -0
  77. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/POSCAR +0 -0
  78. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/README.md +0 -0
  79. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/WAVECAR +0 -0
  80. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/images/bands.png +0 -0
  81. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/images/checktopomat.png +0 -0
  82. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/inputs_maxK/INCAR +0 -0
  83. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/inputs_maxK/KPOINTS +0 -0
  84. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/inputs_maxK/POSCAR +0 -0
  85. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/inputs_maxK/POTCAR +0 -0
  86. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/inputs_scf/INCAR +0 -0
  87. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/inputs_scf/KPOINTS +0 -0
  88. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/inputs_scf/POSCAR +0 -0
  89. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi/inputs_scf/POTCAR +0 -0
  90. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/README.md +0 -0
  91. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/images/bands.png +0 -0
  92. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/images/checktopomat.png +0 -0
  93. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/Bi.txt +0 -0
  94. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/Se.txt +0 -0
  95. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/bands.in +0 -0
  96. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/nscf_IrRep.in +0 -0
  97. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/pp.bands.in +0 -0
  98. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/scf.in +0 -0
  99. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/charge-density.dat +0 -0
  100. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/data-file-schema.xml +0 -0
  101. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/paw.txt +0 -0
  102. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc1.dat +0 -0
  103. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc2.dat +0 -0
  104. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc3.dat +0 -0
  105. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc4.dat +0 -0
  106. {irrep-3.3.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.xml +0 -0
  107. {irrep-3.3.0 → irrep-3.4.0}/tutorial/SnTe/README.md +0 -0
  108. {irrep-3.3.0 → irrep-3.4.0}/tutorial/SnTe/images/bands.png +0 -0
  109. {irrep-3.3.0 → irrep-3.4.0}/tutorial/SnTe/images/checktopomat.png +0 -0
  110. {irrep-3.3.0 → irrep-3.4.0}/tutorial/SnTe/images/refUC.png +0 -0
  111. {irrep-3.3.0 → irrep-3.4.0}/tutorial/SnTe/inputs/Sn.xml +0 -0
  112. {irrep-3.3.0 → irrep-3.4.0}/tutorial/SnTe/inputs/Te.xml +0 -0
  113. {irrep-3.3.0 → irrep-3.4.0}/tutorial/SnTe/inputs/parameters.in +0 -0
  114. {irrep-3.3.0 → irrep-3.4.0}/tutorial/SnTe/maxK_WFK +0 -0
  115. {irrep-3.3.0 → irrep-3.4.0}/uploadpypi.sh +0 -0
@@ -0,0 +1,46 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+
11
+ jobs:
12
+ build:
13
+ strategy:
14
+ max-parallel: 20
15
+ fail-fast: false
16
+ matrix:
17
+ os: [ubuntu-latest]
18
+ python-version: ["3.13"]
19
+ gpaw-version: ["25.7", "26"]
20
+
21
+ runs-on: ${{ matrix.os }}
22
+
23
+ defaults:
24
+ run:
25
+ shell: bash -el {0} # needed so the conda env is activated in every step
26
+
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - name: Set up conda
30
+ uses: conda-incubator/setup-miniconda@v3
31
+ with:
32
+ python-version: ${{ matrix.python-version }}
33
+ channels: conda-forge
34
+ channel-priority: strict
35
+ activate-environment: test
36
+ - name: Install GPAW ${{ matrix.gpaw-version }} from conda-forge
37
+ run: |
38
+ conda install -y "gpaw=${{ matrix.gpaw-version }}" pytest
39
+ python -c "import gpaw; print('GPAW', gpaw.__version__)"
40
+ - name: Install tables from local repo, not from pypi
41
+ working-directory: ./irreptables
42
+ run: pip install -e .
43
+ - name: Install irrep and dependencies
44
+ run: pip install -e .
45
+ - name: pytest
46
+ run: pytest
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: irrep
3
- Version: 3.3.0
3
+ Version: 3.4.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)
@@ -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.3.0'
22
- __version_tuple__ = version_tuple = (3, 3, 0)
21
+ __version__ = version = '3.4.0'
22
+ __version_tuple__ = version_tuple = (3, 4, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g4f43f138d'
24
+ __commit_id__ = commit_id = 'gfe03a819e'
@@ -22,7 +22,7 @@ import functools
22
22
  import numpy as np
23
23
  from functools import cached_property
24
24
 
25
-
25
+ from .parsers.gpaw import check_legacy_gpaw
26
26
  from .parsers import ParserAbinit, ParserVasp, ParserEspresso, ParserW90, ParserGPAW
27
27
  from .kpoint import Kpoint
28
28
  from .spacegroup import SpaceGroup
@@ -531,8 +531,10 @@ class BandStructure:
531
531
  read_paw=False,
532
532
  store_paw=True,
533
533
  storage="tmp_irrep_storage",
534
+ legacy_gpaw=None,
534
535
  **kwargs
535
536
  ):
537
+ legacy_gpaw = check_legacy_gpaw(legacy_gpaw, calculator_gpaw)
536
538
 
537
539
  if spacegroup is None:
538
540
  cls_spacegroup, kwargs_spacegroup = cls.get_spacegroup_cls(spin_channel=spin_channel, irreps=irreps, **kwargs)
@@ -1010,8 +1010,6 @@ class Kpoint(KpointAbstract):
1010
1010
  igmax = igall.max(axis=0)
1011
1011
  igmin = igall.min(axis=0)
1012
1012
  igsize = igmax - igmin + 1
1013
- # print (self.ig.T)
1014
- # print (igsize)
1015
1013
  res = np.zeros((self.num_bands, other.num_bands), dtype=complex)
1016
1014
 
1017
1015
  # short again coefficients of expansions
@@ -34,24 +34,18 @@ class KpointGPAW(KpointAbstract):
34
34
  @classmethod
35
35
  def from_gpaw(self, calc, ibz_index, ispin, RecLattice,
36
36
  IBstart, IBend,
37
- storage
37
+ storage, legacy_gpaw=None
38
38
  ):
39
+ from .parsers.gpaw import read_gpaw_wf
39
40
  ispin = ParserGPAW.spin_channels[ispin]
40
- kpt = calc.wfs.kpt_qs[ibz_index][ispin]
41
- k = calc.get_ibz_k_points()[ibz_index]
42
- nbands = IBend - IBstart
43
- proj = np.hstack([kpt.P_ani[a][IBstart:IBend] for a in range(len(calc.wfs.setups))])
44
-
45
- # Get projections in ibz
46
- if RecLattice is None:
47
- RecLattice = calc.wfs.gd.reciprocal_lattice
48
- atom_positions = calc.atoms.get_scaled_positions()
49
-
50
- wavefunction = np.array([calc.wfs.get_wave_function_array(n, ibz_index, ispin, periodic=True)
51
- for n in range(IBstart, IBend)])
41
+
42
+ k, proj, wavefunction, RecLattice, atom_positions = read_gpaw_wf(
43
+ calc=calc, ibz_index=ibz_index, ispin=ispin, IBstart=IBstart, IBend=IBend, RecLattice=RecLattice,
44
+ legacy_gpaw=legacy_gpaw)
45
+
52
46
  wavefunction = storage.store_array(wavefunction)
53
47
 
54
- return KpointGPAW(kpt=k, wavefunction=wavefunction, proj=proj, nbands=nbands,
48
+ return KpointGPAW(kpt=k, wavefunction=wavefunction, proj=proj, nbands=IBend - IBstart,
55
49
  RecLattice=RecLattice,
56
50
  atom_positions=atom_positions)
57
51
 
@@ -83,12 +77,12 @@ class KpointGPAW(KpointAbstract):
83
77
  class OverlapPAW:
84
78
 
85
79
  def __init__(self, calc, get_soc=True):
86
-
87
80
  self.dO_aii = {}
88
81
  for a, setup in enumerate(calc.wfs.setups):
89
82
  self.dO_aii[a] = np.copy(setup.dO_ii)
90
83
  self.orb_atom_indices = np.cumsum([0] + [dO.shape[0] for dO in self.dO_aii.values()])
91
- self.dv = np.copy(calc.wfs.gd.dv)
84
+ from ase.units import Bohr
85
+ self.dv = np.copy(calc.wfs.gd.dv) * Bohr ** 3
92
86
  if get_soc:
93
87
  from gpaw.spinorbit import soc
94
88
  self.dVL_avii = [
@@ -108,6 +102,9 @@ class OverlapPAW:
108
102
  include_pseudo=True,
109
103
  bk=None,
110
104
  ):
105
+ if bk is None:
106
+ bk = np.zeros(3)
107
+ assert np.allclose(KP2.k - KP1.k, bk), f"K-points must differ by bk for overlap calculation, but KP1={KP1.k}, KP2={KP2.k}, KP2.k - KP1.k = {KP2.k - KP1.k}, bk={bk}"
111
108
  wf1 = KP1.get_wavefunction()
112
109
  proj1 = KP1.proj
113
110
  wf2 = KP2.get_wavefunction()
@@ -119,12 +116,7 @@ class OverlapPAW:
119
116
  if include_pseudo:
120
117
  prod += cached_einsum('aijk,bijk->ab', wf1.conj(), wf2) * self.dv
121
118
  if include_paw:
122
- positions = KP1.atom_positions
123
- na = len(positions)
124
- if bk is None:
125
- phases = np.ones(na, dtype=complex)
126
- else:
127
- phases = np.exp(-2j * np.pi * (positions @ bk))
119
+ phases = np.exp(-2j * np.pi * (KP1.atom_positions @ bk))
128
120
  for a, dO_ii in self.dO_aii.items():
129
121
  I1, I2 = self.get_orb_indices_on_atom(a)
130
122
  prod += (proj1[:, I1:I2].conj() @ dO_ii @ (proj2[:, I1:I2].T)) * phases[a]
@@ -133,6 +125,7 @@ class OverlapPAW:
133
125
 
134
126
  def soc(self, KP1, KP2):
135
127
  from ase.units import Hartree
128
+ assert np.allclose(KP1.k, KP2.k), "K-points must be at the same coordinate for SOC calculation"
136
129
  dVsoc = np.zeros((3, KP1.nbands, KP2.nbands), dtype=complex)
137
130
  for a, dVL_vii in enumerate(self.dVL_avii):
138
131
  I1, I2 = self.get_orb_indices_on_atom(a)
@@ -135,7 +135,6 @@ class ParserEspresso(ParserCommon):
135
135
  checked_files = []
136
136
  for extension in ["hdf5", "dat"]:
137
137
  for strcase in [str.lower, str.upper]:
138
- print(f"{ik=}, {wfcname=}, {extension=}, {strcase=}")
139
138
  filename = f"{self.prefix}.save/{strcase(wfcname)}.{extension}"
140
139
  if os.path.exists(filename):
141
140
  if extension == "hdf5":
@@ -103,11 +103,12 @@ class ParserGPAW(ParserCommon):
103
103
  return energies, WFout, kg, kpt, eKG
104
104
 
105
105
 
106
- def get_soc_gpaw(calc, ik, flatten=True, theta=0, phi=0):
106
+ def get_soc_gpaw(calc, ik, flatten=True, theta=0, phi=0, legacy_gpaw=None):
107
107
  """Calculate SOC Hamiltonian in KS basis for a given k-point."""
108
108
  from ase.units import Hartree
109
109
  from gpaw.spinorbit import soc
110
110
 
111
+ legacy_gpaw = check_legacy_gpaw(calc, legacy_gpaw)
111
112
  nspin = calc.get_number_of_spins()
112
113
 
113
114
  C_ss = np.array(
@@ -150,12 +151,21 @@ def get_soc_gpaw(calc, ik, flatten=True, theta=0, phi=0):
150
151
  for s1 in range(2):
151
152
  for s2 in range(2):
152
153
  h_ii = h_ssii[s1, s2]
153
- if nspin == 2:
154
- P1_mi = calc.wfs.kpt_qs[q][s1].P_ani[a]
155
- P2_mi = calc.wfs.kpt_qs[q][s2].P_ani[a]
156
- if nspin == 1:
157
- P1_mi = calc.wfs.kpt_qs[q][0].P_ani[a]
158
- P2_mi = calc.wfs.kpt_qs[q][0].P_ani[a]
154
+ if legacy_gpaw:
155
+ if nspin == 2:
156
+ P1_mi = calc.wfs.kpt_qs[q][s1].P_ani[a]
157
+ P2_mi = calc.wfs.kpt_qs[q][s2].P_ani[a]
158
+ else:
159
+ P1_mi = calc.wfs.kpt_qs[q][0].P_ani[a]
160
+ P2_mi = P1_mi
161
+ else:
162
+ if nspin == 2:
163
+ # maybe need to use get_wfs_on_master for parallel runs, but needs testing
164
+ P1_mi = calc.dft.ibzwfs._get_wfs(kpt=q, spin=s1).P_ani[a]
165
+ P2_mi = calc.dft.ibzwfs._get_wfs(kpt=q, spin=s2).P_ani[a]
166
+ else:
167
+ P1_mi = calc.dft.ibzwfs._get_wfs(kpt=q, spin=0).P_ani[a]
168
+ P2_mi = P1_mi
159
169
 
160
170
  h_soc[s1, s2] += np.dot(np.dot(P1_mi.conj(), h_ii), P2_mi.T)
161
171
  if flatten:
@@ -165,3 +175,69 @@ def get_soc_gpaw(calc, ik, flatten=True, theta=0, phi=0):
165
175
  for s2 in range(2):
166
176
  h_soc[s1::2, s2::2] = h_soc_old[s1, s2]
167
177
  return h_soc * Hartree
178
+
179
+
180
+ def check_legacy_gpaw(calc, legacy_gpaw=None):
181
+ if legacy_gpaw is not None:
182
+ return legacy_gpaw
183
+ else:
184
+ return not is_new_gpaw(calc)
185
+
186
+
187
+ def is_new_gpaw(calc):
188
+ # "New GPAW" (default in 26): ASECalculator exposing .dft
189
+ # Legacy calculator (25.x, or 26 with legacy_gpaw=True): real calc.wfs
190
+ if hasattr(calc, '_wfs_dft'):
191
+ return True
192
+ if hasattr(calc, 'get_realspace_wfs'):
193
+ return False
194
+ raise ValueError("Cannot determine if GPAW is new or legacy. Please check the GPAW version and calculator type.")
195
+
196
+
197
+
198
+ def is_collinear(calc):
199
+ if hasattr(calc, 'dft'): # new GPAW (26 default)
200
+ return calc.dft.ibzwfs.collinear
201
+ return calc.wfs.collinear
202
+
203
+
204
+ def get_magnetic_moments(calc, project_collinear_axis=(0, 0, 1), legacy_gpaw=None):
205
+ legacy_gpaw = check_legacy_gpaw(calc, legacy_gpaw)
206
+ if legacy_gpaw:
207
+ magmoms = calc.get_magnetic_moments()
208
+ else:
209
+ magmoms = calc.dft.magmoms()[1]
210
+ if is_collinear(calc) and project_collinear_axis:
211
+ magmoms = np.dot(magmoms, project_collinear_axis)
212
+ return magmoms
213
+
214
+
215
+ @staticmethod
216
+ def read_gpaw_wf(calc, ibz_index, ispin, IBstart, IBend, RecLattice, legacy_gpaw=None):
217
+ legacy_gpaw = check_legacy_gpaw(calc, legacy_gpaw)
218
+ k = calc.get_ibz_k_points()[ibz_index]
219
+
220
+ if legacy_gpaw:
221
+ P_ani = calc.wfs.kpt_qs[ibz_index][ispin].P_ani
222
+ if RecLattice is None:
223
+ RecLattice = calc.wfs.gd.reciprocal_lattice
224
+ wavefunction = np.array([
225
+ calc.wfs.get_wave_function_array(n, ibz_index, ispin, periodic=True)
226
+ for n in range(IBstart, IBend)])
227
+ from ase.units import Bohr
228
+ wavefunction /= Bohr ** 1.5
229
+ else:
230
+ wfs = calc.dft.ibzwfs._get_wfs(kpt=ibz_index, spin=ispin)
231
+ P_ani = wfs.P_ani.to_cpu() # AtomArrays; P_ani[a] -> (nbands, nproj_a)
232
+ wavefunction = calc.dft.wave_functions(n1=IBstart, n2=IBend, kpt=ibz_index, spin=ispin, periodic=True).data
233
+ # from ase.units import Bohr
234
+ # wavefunction *= Bohr ** 1.5
235
+
236
+ if RecLattice is None:
237
+ RecLattice = 2 * np.pi * np.linalg.inv(calc.atoms.cell.array).T # check units vs 25.7
238
+
239
+ proj = np.hstack([P_ani[a][IBstart:IBend] for a in range(len(calc.atoms))])
240
+
241
+ atom_positions = calc.atoms.get_scaled_positions()
242
+
243
+ return k, proj, wavefunction, RecLattice, atom_positions
@@ -21,7 +21,8 @@ import warnings
21
21
  import numpy as np
22
22
  import spglib
23
23
 
24
- from irrep.parsers import ParserAbinit, ParserEspresso, ParserVasp, ParserW90
24
+ from .parsers import ParserAbinit, ParserEspresso, ParserVasp, ParserW90
25
+ from .parsers.gpaw import get_magnetic_moments
25
26
 
26
27
  from .symmetry_operation import SymmetryOperation
27
28
  from .utility import BOHR, clean_np_load, group_numbers, log_message, select_irreducible
@@ -254,7 +255,7 @@ class SpaceGroup:
254
255
  """
255
256
  return len(self.symmetries)
256
257
 
257
- def show(self, symmetries=None):
258
+ def show(self, symmetries=None, short=False, to_stdout=True):
258
259
  """
259
260
  Print description of space-group and symmetry operations.
260
261
 
@@ -265,27 +266,26 @@ class SpaceGroup:
265
266
  Run `IrRep` with flag `onlysym` to check the index corresponding
266
267
  to each symmetry operation.
267
268
  """
268
-
269
- print('')
270
- print("\n ---------- CRYSTAL STRUCTURE ---------- \n")
271
- print('')
272
-
273
- # Print cell vectors in DFT cell only
274
- print('Cell vectors in angstroms:\n')
275
- print(f"{'Vectors of DFT cell':^32}")
269
+ out_str = ""
270
+ if not short:
271
+ out_str += "\n ---------- CRYSTAL STRUCTURE ---------- \n\n"
272
+ out_str += 'Cell vectors in angstroms:\n\n'
273
+ out_str += f'{"Vectors of DFT cell":^32}\n'
276
274
  for i in range(3):
277
275
  vec1 = self.real_lattice[i]
278
- print(f'a{i:d} = {vec1[0]:7.4f} {vec1[1]:7.4f} {vec1[2]:7.4f} ')
279
- print()
280
-
281
- print()
282
- print('\n ---------- SPACE GROUP ----------- \n')
283
- print()
284
- print(f'Space group: {self.name} (# {self.number_str})')
285
- print(f'Number of symmetries: {self.size} (mod. lattice translations)')
276
+ out_str += f'a{i:d} = {vec1[0]:7.4f} {vec1[1]:7.4f} {vec1[2]:7.4f} \n'
277
+ out_str += '\n'
278
+
279
+ if not short:
280
+ out_str += '\n ---------- SPACE GROUP ----------- \n\n'
281
+ out_str += f'Space group: {self.name} (# {self.number_str})\n'
282
+ out_str += f'Number of symmetries: {self.size} (mod. lattice translations)\n'
286
283
  for symop in self.symmetries:
287
284
  if symmetries is None or symop.ind in symmetries:
288
- symop.show(refUC=None, shiftUC=None)
285
+ out_str += symop.show(refUC=None, shiftUC=None, to_stdout=False, short=short) + '\n'
286
+ if to_stdout:
287
+ print(out_str)
288
+ return out_str
289
289
 
290
290
  @property
291
291
  def lattice(self):
@@ -903,6 +903,7 @@ class SpaceGroup:
903
903
  magnetic=True,
904
904
  spinor=None,
905
905
  verbosity=0,
906
+ legacy_gpaw=None,
906
907
  **kwargs_cell):
907
908
  """Get the spacegroup of a GPAW calculator (non-spinor only).
908
909
  Parameters
@@ -934,7 +935,7 @@ class SpaceGroup:
934
935
  typat = calculator.atoms.get_atomic_numbers()
935
936
  if magnetic:
936
937
  if magmom is None:
937
- magmom = calculator.get_magnetic_moments()
938
+ magmom = get_magnetic_moments(calculator, legacy_gpaw=legacy_gpaw)
938
939
  assert magmom.shape == (len(calculator.atoms),)
939
940
  else:
940
941
  magmom = np.zeros(len(calculator.atoms), dtype=float)
@@ -954,50 +955,6 @@ class SpaceGroup:
954
955
  **kwargs_cell
955
956
  )
956
957
 
957
- # @classmethod
958
- # def from_gpaw_magnetic(cls, calculator, theta=0, phi=0, include_TR=True,
959
- # magmom=None, magmoms=None,
960
- # mag_symprec=0.05, symprec=1e-5,
961
- # ):
962
- # """
963
- # Get the spacegroup of a GPAW calculator (spinor magnetic version).
964
-
965
- # Parameters:
966
- # calculator : GPAW
967
- # The GPAW calculator.
968
- # theta, phi : float
969
- # The angles defining the global spin quantization axis. (in radians)
970
- # magnetic moments will be aligned along that direction (in positive or negative direction, as defined from dft calculation)
971
- # include_TR : bool
972
- # Whether to include time-reversal symmetry.
973
- # magmom : array( Nat, 3), optional
974
- # if provided, overrides the magnetic moments of the calculator
975
- # magmoms : array(Nat,), optional
976
- # The older argument for magmom, kept for backward compatibility. Used only if magmom is None.
977
- # mag_symprec : float
978
- # The precision for distinguishing different magnetic moments. Only used if magmom is None.
979
- # symprec : float
980
- # The symmetry precision for spacegroup detection.
981
- # """
982
- # lattice = calculator.atoms.cell
983
- # typat = calculator.atoms.get_atomic_numbers()
984
- # positions = calculator.atoms.get_scaled_positions()
985
- # if magmom is None:
986
- # magmom = magmoms
987
- # if magmom is None:
988
- # magmom_axis = calculator.get_magnetic_moments()
989
- # magmom_axis = group_numbers(magmom_axis, precision=mag_symprec)
990
- # axis = np.array([np.sin(theta) * np.cos(phi), np.sin(theta) * np.sin(phi), np.cos(theta)])
991
- # magmom = magmom_axis[:, None] * axis[None, :]
992
- # print(f"using magmom \n {magmom}")
993
- # return SpaceGroup.from_cell(real_lattice=lattice,
994
- # positions=positions,
995
- # typat=typat,
996
- # spinor=True,
997
- # include_TR=include_TR,
998
- # symprec=symprec,
999
- # mag_symprec=mag_symprec,
1000
- # magmom=magmom)
1001
958
 
1002
959
  @classmethod
1003
960
  def get_trivial(cls, lattice, spinor=False, time_reversal=False):
@@ -15,6 +15,7 @@
15
15
  ##################################################################
16
16
 
17
17
 
18
+ from gpaw.rotation import rotation # rotation(l, U_vv) -> (2l+1, 2l+1)
18
19
  from functools import cached_property
19
20
  import warnings
20
21
  import numpy as np
@@ -397,8 +398,15 @@ class SymmetryOperation():
397
398
  S *= self.sign
398
399
  return S
399
400
 
401
+ def show_short(self, to_stdout=False):
402
+ out_str = f"axis: {self.axis.round(6)} ; angle = {self.angle_str}, inversion: {self.inversion}, time reversal: {self.time_reversal}"
403
+ if to_stdout:
404
+ print(out_str)
405
+ return out_str
400
406
 
401
- def show(self, refUC=np.eye(3), shiftUC=np.zeros(3), U=np.eye(2)):
407
+
408
+
409
+ def show(self, refUC=np.eye(3), shiftUC=np.zeros(3), U=np.eye(2), to_stdout=True, short=False):
402
410
  """
403
411
  Print description of symmetry operation.
404
412
 
@@ -413,7 +421,13 @@ class SymmetryOperation():
413
421
  U : array, default=np.zeros(2)
414
422
  Unitary transformation of spin quantization axis from spglib's
415
423
  choice to reference cell's choice
424
+ print : bool, default=True
425
+ If True, print the description. If False, return it as a string.
426
+ short : bool, default=False
427
+ If True, print a short description. If False, print a full description.
416
428
  """
429
+ if short:
430
+ return self.show_short(to_stdout=to_stdout)
417
431
 
418
432
  def parse_row_transform(mrow):
419
433
  s = ""
@@ -447,7 +461,7 @@ class SymmetryOperation():
447
461
  write_ref = False
448
462
 
449
463
  # Print header
450
- print(f"\n ### {self.ind} \n")
464
+ out_str = f"\n ### {self.ind} \n\n"
451
465
 
452
466
  # Print rotation part
453
467
  rotstr = [
@@ -486,8 +500,7 @@ class SymmetryOperation():
486
500
  [parse_row_transform(r) for r in matrix]
487
501
  ) + "]"
488
502
 
489
- print("\n".join(rotstr))
490
- print("\n\n", kstring)
503
+ out_str += "\n".join(rotstr) + "\n\n" + kstring + "\n"
491
504
 
492
505
  # Print spinor transformation matrix
493
506
  if self.spinor:
@@ -497,28 +510,32 @@ class SymmetryOperation():
497
510
  self.spinor_rotation,
498
511
  [" |", " |"])
499
512
  ]
500
- print("\n".join(spinstr))
513
+ out_str += "\n".join(spinstr) + "\n"
501
514
  if write_ref:
502
515
  spinstr = [s + " ".join(f"{x.real:6.3f}{x.imag:+6.3f}j" for x in row) + t
503
516
  for s, row, t in zip(["spinor rot. (refUC) : |", " " * 22 + "|",],
504
517
  self.spinrotation_refUC(U),
505
518
  [" |", " |"])
506
519
  ]
507
- print("\n".join(spinstr))
520
+ out_str += "\n".join(spinstr) + "\n"
508
521
 
509
522
  # Print translation part
510
523
  trastr = ("\ntranslation : [ " +
511
524
  " ".join(f"{x:8.4f}" for x in self.get_transl_mod1(self.translation.round(6))) +
512
525
  " ] ")
513
- print(trastr)
526
+ out_str += trastr
514
527
 
515
528
  if write_ref:
516
529
  _t = self.translation_refUC(refUC, shiftUC)
517
530
  trastr = f"translation (refUC) : [ {' '.join(f'{x:8.4f}' for x in self.get_transl_mod1(_t.round(6)))} ] "
518
- print(trastr)
531
+ out_str += trastr
532
+ out_str += "\n\n"
533
+ out_str += self.show_short(to_stdout=False) + "\n"
534
+
535
+ if to_stdout:
536
+ print(out_str)
537
+ return out_str
519
538
 
520
- print(f"\naxis: {self.axis.round(6)} ; angle = {self.angle_str}, "
521
- f"inversion: {self.inversion}, time reversal: {self.time_reversal}")
522
539
 
523
540
  def copy(self):
524
541
  """
@@ -860,24 +877,20 @@ class SymmetryOperation():
860
877
  def set_R_aii_gpaw(self, calc, reset=False):
861
878
  """rotation matrices for the PAW projections."""
862
879
  if not hasattr(self, 'R_aii') or reset:
863
- from gpaw.atomrotations import AtomRotations
864
- setups = calc.setups
865
-
866
- class SymmetryGpaw1():
867
- def __init__(self, symop):
868
- # self.op_scc = symop.rotation[None, :, :]
869
- # self.cell_cv = symop.real_lattice
870
- self.op_scc = symop.rotation_cart[None, :, :]
871
- self.cell_cv = np.eye(3)
872
- symmetry = SymmetryGpaw1(self)
873
- R_aii = AtomRotations(setups.setups, setups.id_a, symmetry).get_R_asii()
874
- self.R_aii = [R_ii[0] for R_ii in R_aii] # remove unnecessary nesting
880
+ """One block-diagonal projector-space rotation matrix per atom."""
881
+ self.R_aii = []
882
+ for id_ in calc.setups.id_a: # one entry per atom
883
+ setup = calc.setups.setups[id_] # dict: id -> Setup
884
+ n = sum(2 * l + 1 for l in setup.l_j)
885
+ R = np.zeros((n, n))
886
+ i = 0
887
+ for l in setup.l_j:
888
+ m = 2 * l + 1
889
+ R[i:i + m, i:i + m] = rotation(l, self.rotation_cart)
890
+ i += m
891
+ self.R_aii.append(R)
875
892
  self.orb_atom_indices = np.cumsum([0] + [R.shape[0] for R in self.R_aii])
876
893
 
877
- # def get_U_aii_gpaw(self, kpoint):
878
- # """Phase corrected rotation matrices for the PAW projections."""
879
- # return [ R_ii.T * np.exp( 2j * np.pi * np.dot(kpoiMax errnt, self.atom_map_T[a])) for a, R_ii in enumerate(self.R_aii)]
880
- # # return [ R_ii.T for a, R_ii in enumerate(self.R_aii)] # no phase factor, try this
881
894
 
882
895
  def set_gpaw(self, calculator):
883
896
  """Set all gpaw-related attributes."""
@@ -1,3 +1,5 @@
1
+ from packaging.version import parse, Version
2
+ import gpaw
1
3
  from pathlib import Path
2
4
  import os
3
5
 
@@ -6,3 +8,14 @@ REF_DATA_PATH = Path(__file__).parents[0] / "ref_data"
6
8
  TMP_DATA_PATH = Path(__file__).parents[0] / "tmp_data"
7
9
  if not os.path.exists(TMP_DATA_PATH):
8
10
  os.makedirs(TMP_DATA_PATH)
11
+
12
+
13
+ if parse(gpaw.__version__) >= Version("26.0.0"):
14
+ GPAW_VERSION = 26
15
+ else:
16
+ GPAW_VERSION = 25
17
+
18
+
19
+ CALC_TYPE_LIST = ["OLD"]
20
+ if GPAW_VERSION == 26:
21
+ CALC_TYPE_LIST += ["NEW", "LEGACY"]