irrep 3.2.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 (116) hide show
  1. irrep-3.4.0/.github/workflows/tests.yml +46 -0
  2. {irrep-3.2.0 → irrep-3.4.0}/PKG-INFO +1 -1
  3. {irrep-3.2.0 → irrep-3.4.0}/irrep/_version.py +3 -3
  4. {irrep-3.2.0 → irrep-3.4.0}/irrep/altermagnetic_transformer.py +17 -8
  5. {irrep-3.2.0 → irrep-3.4.0}/irrep/bandstructure.py +22 -5
  6. {irrep-3.2.0 → irrep-3.4.0}/irrep/kpoint.py +1 -3
  7. {irrep-3.2.0 → irrep-3.4.0}/irrep/kpoint_gpaw.py +34 -29
  8. {irrep-3.2.0 → irrep-3.4.0}/irrep/parsers/espresso.py +0 -1
  9. {irrep-3.2.0 → irrep-3.4.0}/irrep/parsers/gpaw.py +83 -7
  10. {irrep-3.2.0 → irrep-3.4.0}/irrep/spacegroup.py +43 -65
  11. irrep-3.4.0/irrep/storage.py +69 -0
  12. {irrep-3.2.0 → irrep-3.4.0}/irrep/symmetry_operation.py +43 -27
  13. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/conftest.py +13 -0
  14. irrep-3.4.0/irrep/tests/test_gpaw.py +125 -0
  15. irrep-3.4.0/irrep/tests/test_gpaw_vs_legacy.py +46 -0
  16. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_paw.py +39 -13
  17. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_spacegroup.py +21 -1
  18. {irrep-3.2.0 → irrep-3.4.0}/irrep/utility.py +11 -0
  19. {irrep-3.2.0 → irrep-3.4.0}/irrep.egg-info/PKG-INFO +1 -1
  20. {irrep-3.2.0 → irrep-3.4.0}/irrep.egg-info/SOURCES.txt +2 -0
  21. {irrep-3.2.0 → irrep-3.4.0}/irrep.egg-info/scm_file_list.json +13 -6
  22. irrep-3.4.0/irrep.egg-info/scm_version.json +8 -0
  23. irrep-3.2.0/.github/workflows/tests.yml +0 -42
  24. irrep-3.2.0/irrep/tests/test_gpaw.py +0 -68
  25. irrep-3.2.0/irrep.egg-info/scm_version.json +0 -8
  26. {irrep-3.2.0 → irrep-3.4.0}/.github/workflows/python-publish.yml +0 -0
  27. {irrep-3.2.0 → irrep-3.4.0}/.gitignore +0 -0
  28. {irrep-3.2.0 → irrep-3.4.0}/INSTALL +0 -0
  29. {irrep-3.2.0 → irrep-3.4.0}/LICENSE +0 -0
  30. {irrep-3.2.0 → irrep-3.4.0}/MANIFEST.in +0 -0
  31. {irrep-3.2.0 → irrep-3.4.0}/README.md +0 -0
  32. {irrep-3.2.0 → irrep-3.4.0}/irrep/__init__.py +0 -0
  33. {irrep-3.2.0 → irrep-3.4.0}/irrep/cli.py +0 -0
  34. {irrep-3.2.0 → irrep-3.4.0}/irrep/data/msg_numbers.data +0 -0
  35. {irrep-3.2.0 → irrep-3.4.0}/irrep/ebrs.py +0 -0
  36. {irrep-3.2.0 → irrep-3.4.0}/irrep/gvectors.py +0 -0
  37. {irrep-3.2.0 → irrep-3.4.0}/irrep/or_solutions_obtainer.py +0 -0
  38. {irrep-3.2.0 → irrep-3.4.0}/irrep/parsers/__init__.py +0 -0
  39. {irrep-3.2.0 → irrep-3.4.0}/irrep/parsers/abinit.py +0 -0
  40. {irrep-3.2.0 → irrep-3.4.0}/irrep/parsers/common.py +0 -0
  41. {irrep-3.2.0 → irrep-3.4.0}/irrep/parsers/parse_files_bandstructure.py +0 -0
  42. {irrep-3.2.0 → irrep-3.4.0}/irrep/parsers/vasp.py +0 -0
  43. {irrep-3.2.0 → irrep-3.4.0}/irrep/parsers/wannier90.py +0 -0
  44. {irrep-3.2.0 → irrep-3.4.0}/irrep/spacegroup_irreps.py +0 -0
  45. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/__init__.py +0 -0
  46. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=False.pkl +0 -0
  47. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=False_irred.pkl +0 -0
  48. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=True.pkl +0 -0
  49. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/Fe_qe_dmn_TR=True_irred.pkl +0 -0
  50. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/magmom_Fe_qe_dmn.txt +0 -0
  51. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/spacegroup_Te_TR.npz +0 -0
  52. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/spacegroup_Te_TR_product.npz +0 -0
  53. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/spacegroup_Te_noTR.npz +0 -0
  54. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/spacegroup_Te_noTR_product.npz +0 -0
  55. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/symm_matrix_block.npz +0 -0
  56. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/ref_data/symm_matrix_full.npz +0 -0
  57. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_abinit_scalar.py +0 -0
  58. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_abinit_spinor.py +0 -0
  59. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_altermagnetic.py +0 -0
  60. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_dmn.py +0 -0
  61. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_ebr_decomposition.py +0 -0
  62. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_espresso_hdf5.py +0 -0
  63. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_espresso_spinor.py +0 -0
  64. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_espresso_spinpol.py +0 -0
  65. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_refUC.py +0 -0
  66. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_symmetry_indicators.py +0 -0
  67. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_symsep.py +0 -0
  68. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_vasp_scalar.py +0 -0
  69. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_vasp_spinor.py +0 -0
  70. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_wannier_scalar.py +0 -0
  71. {irrep-3.2.0 → irrep-3.4.0}/irrep/tests/test_wannier_spinor.py +0 -0
  72. {irrep-3.2.0 → irrep-3.4.0}/irrep.egg-info/dependency_links.txt +0 -0
  73. {irrep-3.2.0 → irrep-3.4.0}/irrep.egg-info/entry_points.txt +0 -0
  74. {irrep-3.2.0 → irrep-3.4.0}/irrep.egg-info/requires.txt +0 -0
  75. {irrep-3.2.0 → irrep-3.4.0}/irrep.egg-info/top_level.txt +0 -0
  76. {irrep-3.2.0 → irrep-3.4.0}/pyproject.toml +0 -0
  77. {irrep-3.2.0 → irrep-3.4.0}/setup.cfg +0 -0
  78. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/POSCAR +0 -0
  79. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/README.md +0 -0
  80. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/WAVECAR +0 -0
  81. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/images/bands.png +0 -0
  82. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/images/checktopomat.png +0 -0
  83. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/inputs_maxK/INCAR +0 -0
  84. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/inputs_maxK/KPOINTS +0 -0
  85. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/inputs_maxK/POSCAR +0 -0
  86. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/inputs_maxK/POTCAR +0 -0
  87. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/inputs_scf/INCAR +0 -0
  88. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/inputs_scf/KPOINTS +0 -0
  89. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/inputs_scf/POSCAR +0 -0
  90. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi/inputs_scf/POTCAR +0 -0
  91. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/README.md +0 -0
  92. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/images/bands.png +0 -0
  93. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/images/checktopomat.png +0 -0
  94. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/Bi.txt +0 -0
  95. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/Se.txt +0 -0
  96. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/bands.in +0 -0
  97. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/nscf_IrRep.in +0 -0
  98. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/pp.bands.in +0 -0
  99. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/inputs/scf.in +0 -0
  100. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/charge-density.dat +0 -0
  101. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/data-file-schema.xml +0 -0
  102. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/paw.txt +0 -0
  103. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc1.dat +0 -0
  104. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc2.dat +0 -0
  105. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc3.dat +0 -0
  106. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.save/wfc4.dat +0 -0
  107. {irrep-3.2.0 → irrep-3.4.0}/tutorial/Bi2Se3/out/Bi2Se3.xml +0 -0
  108. {irrep-3.2.0 → irrep-3.4.0}/tutorial/SnTe/README.md +0 -0
  109. {irrep-3.2.0 → irrep-3.4.0}/tutorial/SnTe/images/bands.png +0 -0
  110. {irrep-3.2.0 → irrep-3.4.0}/tutorial/SnTe/images/checktopomat.png +0 -0
  111. {irrep-3.2.0 → irrep-3.4.0}/tutorial/SnTe/images/refUC.png +0 -0
  112. {irrep-3.2.0 → irrep-3.4.0}/tutorial/SnTe/inputs/Sn.xml +0 -0
  113. {irrep-3.2.0 → irrep-3.4.0}/tutorial/SnTe/inputs/Te.xml +0 -0
  114. {irrep-3.2.0 → irrep-3.4.0}/tutorial/SnTe/inputs/parameters.in +0 -0
  115. {irrep-3.2.0 → irrep-3.4.0}/tutorial/SnTe/maxK_WFK +0 -0
  116. {irrep-3.2.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.2.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.2.0'
22
- __version_tuple__ = version_tuple = (3, 2, 0)
21
+ __version__ = version = '3.4.0'
22
+ __version_tuple__ = version_tuple = (3, 4, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g5a82e29d3'
24
+ __commit_id__ = commit_id = 'gfe03a819e'
@@ -42,27 +42,29 @@ class AltermagneticTransformer:
42
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
43
 
44
44
  @classmethod
45
- def from_gpaw(cls, calculator, symmetrizer_up, nskip_symmetries=0):
45
+ def from_gpaw(cls, calculator, symmetrizer_up, nskip_symmetries=0, tol_magmom=1e-4):
46
46
  symop = find_altermagnetic_symmetry(lattice=calculator.atoms.cell,
47
47
  positions=calculator.atoms.get_scaled_positions(),
48
48
  typat=calculator.atoms.get_atomic_numbers(),
49
49
  magmom=calculator.get_magnetic_moments(),
50
- nskip=nskip_symmetries)
50
+ nskip=nskip_symmetries,
51
+ tol=tol_magmom)
51
52
  return cls(calculator, symmetrizer_up, rotation_latt=symop.rotation, translation_latt=symop.translation)
52
53
 
53
54
  def get_kpoint_down(self, ikirr, kpoints_up):
54
55
  ik_origin = self.alter_map[ikirr]
55
56
  KPtransformed = kpoints_up[ik_origin].get_transformed_copy(
56
57
  symmetry_operation=self.symmetry_operations[ikirr],
57
- k_new=self.k_intermediate[ikirr])
58
+ k_new=self.k_intermediate[ikirr], store=False)
58
59
  KPtransformed = KPtransformed.get_transformed_copy(
59
60
  symmetry_operation=self.alter_symop,
60
- k_new=kpoints_up[ikirr].k)
61
+ k_new=kpoints_up[ikirr].k, store=False)
61
62
  # TODO : unify the transformations, using just their product
62
63
  return KPtransformed
63
64
 
64
65
 
65
- def find_altermagnetic_symmetry(lattice, positions, typat, magmom, nskip=0):
66
+ def find_altermagnetic_symmetry(lattice, positions, typat, magmom, nskip=0,
67
+ all=False, tol=1e-4):
66
68
  magmom = np.array(magmom)
67
69
  assert magmom.ndim == 1, "Magnetic moments must be a 1D array."
68
70
  positions = np.array(positions)
@@ -76,15 +78,22 @@ def find_altermagnetic_symmetry(lattice, positions, typat, magmom, nskip=0):
76
78
  if nskip >= len(spacegroup_nomag.symmetries) // 2:
77
79
  raise ValueError(f"nskip={nskip} is too large for the number of symmetries={len(spacegroup_nomag.symmetries)}.")
78
80
  cnt = 0
81
+ if all:
82
+ symop_list = []
79
83
  for symop in spacegroup_nomag.symmetries:
80
84
  atommap, T = get_atom_map(symop, positions)
81
85
  # print (f"Checking symmetry operation: {symop}, {atommap=}, {T=}")
82
86
  assert np.all(typat[atommap] == typat), "Atom types must match under symmetry operation."
83
87
  magmom_transformed = magmom[atommap]
84
- print(f"{magmom_transformed=}, {atommap=},")
85
- if np.allclose(magmom_transformed, -magmom):
88
+ print(f"{magmom_transformed=}, {atommap=}, m-transformed={magmom_transformed + magmom}")
89
+ if np.allclose(magmom_transformed, -magmom, atol=tol):
86
90
  cnt += 1
87
91
  if cnt > nskip:
88
- return symop
92
+ if all:
93
+ symop_list.append(symop)
94
+ else:
95
+ return symop
96
+ if all:
97
+ return symop_list
89
98
  else:
90
99
  raise ValueError("No altermagnetic symmetry operation found for the given magnetic moments.")
@@ -22,14 +22,14 @@ 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
29
29
  from .spacegroup_irreps import SpaceGroupIrreps
30
30
  from .gvectors import sortIG, calc_gvectors
31
31
  from .utility import get_block_indices, get_mapping_irr, grid_from_kpoints, log_message, UniqueListMod1, restore_full_grid, select_irreducible
32
-
32
+ from .storage import DummyStorage
33
33
  try:
34
34
  from irreptables.ebrs import load_ebr_data
35
35
  except ImportError:
@@ -181,9 +181,10 @@ class BandStructure:
181
181
  efermi=0.0,
182
182
  Ecut=None,
183
183
  kwargs_kpoint=None,
184
+ storage=None,
184
185
  **kwargs_parser):
185
186
  if len(kwargs_parser) > 0:
186
- raise ValueError(f"the following kwargs are not recognized by BandStructure-__init__(): {list(kwargs_parser.keys())}, please use BandStructure.from_???() class methods to parse files instead of calling BandStructure.__init__() directly.")
187
+ raise ValueError(f"the following kwargs are not recognized by BandStructure-__init__(): {list(kwargs_parser.keys())}, \n If you meant the old API, please use BandStructure.from_???() class methods to parse files instead of calling BandStructure.__init__() directly.")
187
188
  self.kpoints = kpoints
188
189
  if kpoints_paw is not None:
189
190
  self.kpoints_paw = kpoints_paw
@@ -193,6 +194,9 @@ class BandStructure:
193
194
  self.efermi = efermi
194
195
  self.Ecut = Ecut
195
196
  self.kwargs_kpoint = kwargs_kpoint
197
+ if storage is None:
198
+ storage = DummyStorage()
199
+ self.storage = storage
196
200
  self.kpoint_getter = kpoint_getter
197
201
  self.kpoint_getter_paw = kpoint_getter_paw
198
202
  self.kplist = kplist
@@ -525,8 +529,12 @@ class BandStructure:
525
529
  spacegroup=None,
526
530
  read_kpoints=True,
527
531
  read_paw=False,
532
+ store_paw=True,
533
+ storage="tmp_irrep_storage",
534
+ legacy_gpaw=None,
528
535
  **kwargs
529
536
  ):
537
+ legacy_gpaw = check_legacy_gpaw(legacy_gpaw, calculator_gpaw)
530
538
 
531
539
  if spacegroup is None:
532
540
  cls_spacegroup, kwargs_spacegroup = cls.get_spacegroup_cls(spin_channel=spin_channel, irreps=irreps, **kwargs)
@@ -542,6 +550,13 @@ class BandStructure:
542
550
  verbosity=verbosity
543
551
  )
544
552
  if read_paw:
553
+ if isinstance(storage, str):
554
+ if store_paw:
555
+ from .storage import Storage
556
+ storage = Storage(storage)
557
+ else:
558
+ from .storage import DummyStorage
559
+ storage = DummyStorage()
545
560
  spacegroup.set_gpaw(calculator=parser.calculator)
546
561
  from irrep.kpoint_gpaw import OverlapPAW
547
562
  overlap_paw = OverlapPAW(calc=parser.calculator)
@@ -574,7 +589,8 @@ class BandStructure:
574
589
  from irrep.kpoint_gpaw import KpointGPAW
575
590
  kp_gpaw = KpointGPAW.from_gpaw(calc=calculator_gpaw, ibz_index=ik, ispin=spin_channel,
576
591
  RecLattice=spacegroup.reciprocal_lattice,
577
- IBstart=IBstart, IBend=IBend)
592
+ IBstart=IBstart, IBend=IBend,
593
+ storage=storage)
578
594
  return kp_gpaw
579
595
  else:
580
596
  get_kpoint_paw = None
@@ -582,7 +598,8 @@ class BandStructure:
582
598
  bandstr = cls(kplist=kplist_noskip, spacegroup=spacegroup, Ecut=Ecut, mp_grid=mp_grid, efermi=efermi,
583
599
  overlap_paw=overlap_paw if read_paw else None,
584
600
  kpoint_getter_paw=get_kpoint_paw,
585
- kpoint_getter=get_kpoint)
601
+ kpoint_getter=get_kpoint,
602
+ storage=storage)
586
603
  if read_kpoints:
587
604
  bandstr.read_all_kpoints()
588
605
  if read_paw:
@@ -751,7 +751,7 @@ class Kpoint(KpointAbstract):
751
751
 
752
752
 
753
753
 
754
- def get_transformed_copy(self, symmetry_operation, k_new=None):
754
+ def get_transformed_copy(self, symmetry_operation, k_new=None, store=False):
755
755
  """
756
756
  Get a copy of the k-point transformed by a symmetry operation.
757
757
 
@@ -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
@@ -1,4 +1,6 @@
1
1
  import numpy as np
2
+
3
+ from irrep.storage import DummyStorage
2
4
  from .kpoint import KpointAbstract
3
5
  from .utility import cached_einsum
4
6
  from .parsers import ParserGPAW
@@ -24,35 +26,33 @@ class KpointGPAW(KpointAbstract):
24
26
  RecLattice=None,
25
27
  atom_positions=None):
26
28
  super().__init__(kpt=kpt, num_bands=nbands, RecLattice=RecLattice)
27
- self.wavefunction = wavefunction
29
+ self._wavefunction = wavefunction
28
30
  self.proj = proj
29
31
  self.nbands = nbands
30
32
  self.atom_positions = atom_positions
31
33
 
32
34
  @classmethod
33
35
  def from_gpaw(self, calc, ibz_index, ispin, RecLattice,
34
- IBstart, IBend
36
+ IBstart, IBend,
37
+ storage, legacy_gpaw=None
35
38
  ):
39
+ from .parsers.gpaw import read_gpaw_wf
36
40
  ispin = ParserGPAW.spin_channels[ispin]
37
- kpt = calc.wfs.kpt_qs[ibz_index][ispin]
38
- k = calc.get_ibz_k_points()[ibz_index]
39
- nbands = IBend - IBstart
40
- proj = np.hstack([kpt.P_ani[a][IBstart:IBend] for a in range(len(calc.wfs.setups))])
41
-
42
- # Get projections in ibz
43
- if RecLattice is None:
44
- RecLattice = calc.wfs.gd.reciprocal_lattice
45
- atom_positions = calc.atoms.get_scaled_positions()
46
-
47
- wavefunction = np.array([calc.wfs.get_wave_function_array(n, ibz_index, ispin, periodic=True)
48
- for n in range(IBstart, IBend)])
49
- return KpointGPAW(kpt=k, wavefunction=wavefunction, proj=proj, nbands=nbands,
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
+
46
+ wavefunction = storage.store_array(wavefunction)
47
+
48
+ return KpointGPAW(kpt=k, wavefunction=wavefunction, proj=proj, nbands=IBend - IBstart,
50
49
  RecLattice=RecLattice,
51
50
  atom_positions=atom_positions)
52
51
 
52
+ def get_wavefunction(self):
53
+ return self._wavefunction.get()
53
54
 
54
-
55
- def get_transformed_copy(self, symmetry_operation, k_new):
55
+ def get_transformed_copy(self, symmetry_operation, k_new, store=False):
56
56
  """Get a transformed copy of the KpointGPAW object according to the given symmetry operation.
57
57
 
58
58
  Args:
@@ -62,21 +62,27 @@ class KpointGPAW(KpointAbstract):
62
62
  Returns:
63
63
  KpointGPAW: Transformed copy of the KpointGPAW object.
64
64
  """
65
- new_wavefunction = symmetry_operation.rotate_pseudo_wavefunction(psi_n_grid=self.wavefunction, k_origin=self.k, k_target=k_new)
65
+ new_wavefunction = symmetry_operation.rotate_pseudo_wavefunction(psi_n_grid=self.get_wavefunction(), k_origin=self.k, k_target=k_new)
66
66
  new_proj = symmetry_operation.rotate_projection(self.proj, self.k, k_new)
67
+ if store:
68
+ storage = self._wavefunction.storage
69
+ else:
70
+ storage = DummyStorage()
71
+ new_wavefunction = storage.store_array(new_wavefunction)
67
72
  return KpointGPAW(kpt=k_new, wavefunction=new_wavefunction, proj=new_proj, nbands=self.nbands,
68
- RecLattice=self.RecLattice, atom_positions=self.atom_positions)
73
+ RecLattice=self.RecLattice, atom_positions=self.atom_positions,
74
+ )
69
75
 
70
76
 
71
77
  class OverlapPAW:
72
78
 
73
79
  def __init__(self, calc, get_soc=True):
74
-
75
80
  self.dO_aii = {}
76
81
  for a, setup in enumerate(calc.wfs.setups):
77
82
  self.dO_aii[a] = np.copy(setup.dO_ii)
78
83
  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)
84
+ from ase.units import Bohr
85
+ self.dv = np.copy(calc.wfs.gd.dv) * Bohr ** 3
80
86
  if get_soc:
81
87
  from gpaw.spinorbit import soc
82
88
  self.dVL_avii = [
@@ -96,9 +102,12 @@ class OverlapPAW:
96
102
  include_pseudo=True,
97
103
  bk=None,
98
104
  ):
99
- wf1 = KP1.wavefunction
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}"
108
+ wf1 = KP1.get_wavefunction()
100
109
  proj1 = KP1.proj
101
- wf2 = KP2.wavefunction
110
+ wf2 = KP2.get_wavefunction()
102
111
  proj2 = KP2.proj
103
112
  assert wf1.ndim == 4
104
113
  assert wf2.ndim == 4
@@ -107,12 +116,7 @@ class OverlapPAW:
107
116
  if include_pseudo:
108
117
  prod += cached_einsum('aijk,bijk->ab', wf1.conj(), wf2) * self.dv
109
118
  if include_paw:
110
- positions = KP1.atom_positions
111
- na = len(positions)
112
- if bk is None:
113
- phases = np.ones(na, dtype=complex)
114
- else:
115
- phases = np.exp(-2j * np.pi * (positions @ bk))
119
+ phases = np.exp(-2j * np.pi * (KP1.atom_positions @ bk))
116
120
  for a, dO_ii in self.dO_aii.items():
117
121
  I1, I2 = self.get_orb_indices_on_atom(a)
118
122
  prod += (proj1[:, I1:I2].conj() @ dO_ii @ (proj2[:, I1:I2].T)) * phases[a]
@@ -121,6 +125,7 @@ class OverlapPAW:
121
125
 
122
126
  def soc(self, KP1, KP2):
123
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"
124
129
  dVsoc = np.zeros((3, KP1.nbands, KP2.nbands), dtype=complex)
125
130
  for a, dVL_vii in enumerate(self.dVL_avii):
126
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,10 +21,11 @@ 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
- from .utility import BOHR, group_numbers, log_message, select_irreducible
28
+ from .utility import BOHR, clean_np_load, group_numbers, log_message, select_irreducible
28
29
  from packaging import version
29
30
  import os
30
31
 
@@ -226,6 +227,27 @@ class SpaceGroup:
226
227
  dic[key] = val
227
228
  return dic
228
229
 
230
+ @classmethod
231
+ def from_dict(cls, dic):
232
+ """
233
+ Create a SpaceGroup object from a dictionary.
234
+ """
235
+ dic_clean = clean_np_load(dic)
236
+ return cls(**dic_clean)
237
+
238
+ @classmethod
239
+ def from_npz(cls, file_path):
240
+ """
241
+ Create a SpaceGroup object from a .npz file.
242
+ """
243
+ return cls.from_dict(np.load(file_path, allow_pickle=True))
244
+
245
+ def to_npz(self, file_path):
246
+ """
247
+ Save the SpaceGroup object to a .npz file.
248
+ """
249
+ np.savez(file_path, **self.as_dict())
250
+
229
251
  @property
230
252
  def size(self):
231
253
  """
@@ -233,7 +255,7 @@ class SpaceGroup:
233
255
  """
234
256
  return len(self.symmetries)
235
257
 
236
- def show(self, symmetries=None):
258
+ def show(self, symmetries=None, short=False, to_stdout=True):
237
259
  """
238
260
  Print description of space-group and symmetry operations.
239
261
 
@@ -244,27 +266,26 @@ class SpaceGroup:
244
266
  Run `IrRep` with flag `onlysym` to check the index corresponding
245
267
  to each symmetry operation.
246
268
  """
247
-
248
- print('')
249
- print("\n ---------- CRYSTAL STRUCTURE ---------- \n")
250
- print('')
251
-
252
- # Print cell vectors in DFT cell only
253
- print('Cell vectors in angstroms:\n')
254
- 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'
255
274
  for i in range(3):
256
275
  vec1 = self.real_lattice[i]
257
- print(f'a{i:d} = {vec1[0]:7.4f} {vec1[1]:7.4f} {vec1[2]:7.4f} ')
258
- print()
259
-
260
- print()
261
- print('\n ---------- SPACE GROUP ----------- \n')
262
- print()
263
- print(f'Space group: {self.name} (# {self.number_str})')
264
- 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'
265
283
  for symop in self.symmetries:
266
284
  if symmetries is None or symop.ind in symmetries:
267
- 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
268
289
 
269
290
  @property
270
291
  def lattice(self):
@@ -882,6 +903,7 @@ class SpaceGroup:
882
903
  magnetic=True,
883
904
  spinor=None,
884
905
  verbosity=0,
906
+ legacy_gpaw=None,
885
907
  **kwargs_cell):
886
908
  """Get the spacegroup of a GPAW calculator (non-spinor only).
887
909
  Parameters
@@ -913,7 +935,7 @@ class SpaceGroup:
913
935
  typat = calculator.atoms.get_atomic_numbers()
914
936
  if magnetic:
915
937
  if magmom is None:
916
- magmom = calculator.get_magnetic_moments()
938
+ magmom = get_magnetic_moments(calculator, legacy_gpaw=legacy_gpaw)
917
939
  assert magmom.shape == (len(calculator.atoms),)
918
940
  else:
919
941
  magmom = np.zeros(len(calculator.atoms), dtype=float)
@@ -933,50 +955,6 @@ class SpaceGroup:
933
955
  **kwargs_cell
934
956
  )
935
957
 
936
- # @classmethod
937
- # def from_gpaw_magnetic(cls, calculator, theta=0, phi=0, include_TR=True,
938
- # magmom=None, magmoms=None,
939
- # mag_symprec=0.05, symprec=1e-5,
940
- # ):
941
- # """
942
- # Get the spacegroup of a GPAW calculator (spinor magnetic version).
943
-
944
- # Parameters:
945
- # calculator : GPAW
946
- # The GPAW calculator.
947
- # theta, phi : float
948
- # The angles defining the global spin quantization axis. (in radians)
949
- # magnetic moments will be aligned along that direction (in positive or negative direction, as defined from dft calculation)
950
- # include_TR : bool
951
- # Whether to include time-reversal symmetry.
952
- # magmom : array( Nat, 3), optional
953
- # if provided, overrides the magnetic moments of the calculator
954
- # magmoms : array(Nat,), optional
955
- # The older argument for magmom, kept for backward compatibility. Used only if magmom is None.
956
- # mag_symprec : float
957
- # The precision for distinguishing different magnetic moments. Only used if magmom is None.
958
- # symprec : float
959
- # The symmetry precision for spacegroup detection.
960
- # """
961
- # lattice = calculator.atoms.cell
962
- # typat = calculator.atoms.get_atomic_numbers()
963
- # positions = calculator.atoms.get_scaled_positions()
964
- # if magmom is None:
965
- # magmom = magmoms
966
- # if magmom is None:
967
- # magmom_axis = calculator.get_magnetic_moments()
968
- # magmom_axis = group_numbers(magmom_axis, precision=mag_symprec)
969
- # axis = np.array([np.sin(theta) * np.cos(phi), np.sin(theta) * np.sin(phi), np.cos(theta)])
970
- # magmom = magmom_axis[:, None] * axis[None, :]
971
- # print(f"using magmom \n {magmom}")
972
- # return SpaceGroup.from_cell(real_lattice=lattice,
973
- # positions=positions,
974
- # typat=typat,
975
- # spinor=True,
976
- # include_TR=include_TR,
977
- # symprec=symprec,
978
- # mag_symprec=mag_symprec,
979
- # magmom=magmom)
980
958
 
981
959
  @classmethod
982
960
  def get_trivial(cls, lattice, spinor=False, time_reversal=False):