TB2J 0.9.12.18__py3-none-any.whl → 0.9.12.23__py3-none-any.whl

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 (40) hide show
  1. TB2J/MAE.py +8 -1
  2. TB2J/MAEGreen.py +0 -2
  3. TB2J/exchange.py +11 -4
  4. TB2J/exchangeCL2.py +2 -0
  5. TB2J/exchange_params.py +24 -0
  6. TB2J/green.py +15 -3
  7. TB2J/interfaces/abacus/gen_exchange_abacus.py +2 -1
  8. TB2J/io_exchange/__init__.py +19 -1
  9. TB2J/io_exchange/edit.py +594 -0
  10. TB2J/io_exchange/io_exchange.py +243 -74
  11. TB2J/io_exchange/io_tomsasd.py +4 -3
  12. TB2J/io_exchange/io_txt.py +72 -7
  13. TB2J/io_exchange/io_vampire.py +1 -1
  14. TB2J/io_merge.py +60 -40
  15. TB2J/magnon/magnon3.py +27 -2
  16. TB2J/mathutils/rotate_spin.py +7 -7
  17. TB2J/mycfr.py +16 -16
  18. TB2J/plot.py +26 -0
  19. TB2J/scripts/TB2J_edit.py +403 -0
  20. TB2J/scripts/TB2J_plot_exchange.py +48 -0
  21. TB2J/spinham/hamiltonian.py +156 -13
  22. TB2J/spinham/hamiltonian_terms.py +40 -1
  23. TB2J/spinham/spin_xml.py +40 -8
  24. TB2J/symmetrize_J.py +138 -7
  25. TB2J/tests/test_cli_remove_sublattice.py +33 -0
  26. TB2J/tests/test_cli_toggle_exchange.py +50 -0
  27. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.23.dist-info}/METADATA +7 -3
  28. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.23.dist-info}/RECORD +32 -35
  29. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.23.dist-info}/WHEEL +1 -1
  30. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.23.dist-info}/entry_points.txt +2 -0
  31. TB2J/.gitignore +0 -5
  32. TB2J/agent_files/debug_spinphon_fd/debug_main.py +0 -156
  33. TB2J/agent_files/debug_spinphon_fd/test_compute_dJdx.py +0 -272
  34. TB2J/agent_files/debug_spinphon_fd/test_ispin0_only.py +0 -120
  35. TB2J/agent_files/debug_spinphon_fd/test_no_d2j.py +0 -31
  36. TB2J/agent_files/debug_spinphon_fd/test_with_d2j.py +0 -28
  37. TB2J/interfaces/abacus/test_read_HRSR.py +0 -43
  38. TB2J/interfaces/abacus/test_read_stru.py +0 -32
  39. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.23.dist-info}/licenses/LICENSE +0 -0
  40. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.23.dist-info}/top_level.txt +0 -0
@@ -50,7 +50,7 @@ def write_vampire_unitcell_file(cls, fname):
50
50
  counter = -1
51
51
  for key in cls.exchange_Jdict:
52
52
  R, ispin, jspin = key
53
- Jtensor = cls.get_J_tensor(ispin, jspin, R)
53
+ Jtensor = cls.get_J_tensor(ispin, jspin, R, Jani=True, DMI=True)
54
54
  counter += 1 # starts at 0
55
55
  myfile.write(
56
56
  f"{counter:5d} {ispin:3d} {jspin:3d} {R[0]:3d} {R[1]:3d} {R[2]:3d} "
TB2J/io_merge.py CHANGED
@@ -107,22 +107,23 @@ class Merger:
107
107
  projv = {}
108
108
  coeff_matrix = {}
109
109
  projectors = {}
110
- for key in self.main_dat.projv.keys():
111
- vectors = [obj.projv[key] for obj in self.dat]
112
- coefficients, u, v = zip(
113
- *[get_Jani_coefficients(vectors[i], R=R[i]) for i in indices]
114
- )
115
- projectors[key] = np.vstack([u[i] @ R[i].T for i in indices])
116
- coeff_matrix[key] = np.vstack(coefficients)
117
- proju[key] = np.stack(u)
118
- projv[key] = np.stack(v)
119
- if np.linalg.matrix_rank(coeff_matrix[key], tol=1e-2) < 6:
120
- warnings.warn("""
121
- WARNING: The matrix of equations to reconstruct the exchange tensors is
122
- close to being singular. This happens when the magnetic moments between
123
- different configurations are cloes to being parallel. You need to consider
124
- more rotated spin configurations, otherwise the results might have a large
125
- error.""")
110
+ if self.main_dat.projv is not None:
111
+ for key in self.main_dat.projv.keys():
112
+ vectors = [obj.projv[key] for obj in self.dat]
113
+ coefficients, u, v = zip(
114
+ *[get_Jani_coefficients(vectors[i], R=R[i]) for i in indices]
115
+ )
116
+ projectors[key] = np.vstack([u[i] @ R[i].T for i in indices])
117
+ coeff_matrix[key] = np.vstack(coefficients)
118
+ proju[key] = np.stack(u)
119
+ projv[key] = np.stack(v)
120
+ if np.linalg.matrix_rank(coeff_matrix[key], tol=1e-2) < 6:
121
+ warnings.warn("""
122
+ WARNING: The matrix of equations to reconstruct the exchange tensors is
123
+ close to being singular. This happens when the magnetic moments between
124
+ different configurations are cloes to being parallel. You need to consider
125
+ more rotated spin configurations, otherwise the results might have a large
126
+ error.""")
126
127
 
127
128
  self.proju = proju
128
129
  self.projv = projv
@@ -134,13 +135,23 @@ class Merger:
134
135
  proju = self.proju
135
136
  projv = self.projv
136
137
  coeff_matrix = self.coeff_matrix
137
- for key in self.main_dat.Jani_dict.keys():
138
+ for key in self.main_dat.exchange_Jdict.keys():
138
139
  try:
139
140
  R, i, j = key
140
141
  u = proju[i, j]
141
142
  v = projv[i, j]
142
- Jani = np.stack([sio.Jani_dict[key] for sio in self.dat])
143
- projections = np.einsum("nmi,nij,nmj->nm", u, Jani, v).flatten()
143
+ projections = []
144
+ for idx, sio in enumerate(self.dat):
145
+ if sio.has_bilinear and key in sio.Jani_dict:
146
+ Jmat = sio.get_J_tensor(
147
+ i, j, R, Jiso=False, Jani=True, DMI=False
148
+ )
149
+ else:
150
+ # J_scalar = sio.exchange_Jdict.get(key, 0.0)
151
+ Jmat = np.eye(3) * 0.0
152
+ proj = np.einsum("mi,ij,mj->m", u[idx], Jmat, v[idx])
153
+ projections.append(proj)
154
+ projections = np.concatenate(projections)
144
155
  except KeyError as err:
145
156
  raise KeyError(
146
157
  "Can not find key: %s, Please make sure the three calculations use the same k-mesh and same Rcut."
@@ -158,28 +169,36 @@ class Merger:
158
169
 
159
170
  def merge_Jiso(self):
160
171
  Jdict = {}
161
- for key in self.main_dat.exchange_Jdict.keys():
162
- try:
163
- J = np.mean([obj.exchange_Jdict[key] for obj in self.dat])
164
- except KeyError as err:
165
- raise KeyError(
166
- "Can not find key: %s, Please make sure the three calculations use the same k-mesh and same Rcut."
167
- % err
168
- )
169
- Jdict[key] = J
170
- self.main_dat.exchange_Jdict = Jdict
172
+ if self.main_dat.exchange_Jdict is not None:
173
+ for key in self.main_dat.exchange_Jdict.keys():
174
+ try:
175
+ J = np.mean([obj.exchange_Jdict[key] for obj in self.dat])
176
+ except KeyError as err:
177
+ raise KeyError(
178
+ "Can not find key: %s, Please make sure the three calculations use the same k-mesh and same Rcut."
179
+ % err
180
+ )
181
+ Jdict[key] = J
182
+ self.main_dat.exchange_Jdict = Jdict
171
183
 
172
184
  def merge_DMI(self):
173
185
  dmi_ddict = {}
174
- if all(obj.has_dmi for obj in self.dat):
186
+ if any(obj.has_dmi for obj in self.dat):
175
187
  projectors = self.projectors
176
188
  proju = self.proju
177
- for key in self.main_dat.dmi_ddict.keys():
189
+ for key in self.main_dat.exchange_Jdict.keys():
178
190
  try:
179
191
  R, i, j = key
180
192
  u = proju[i, j]
181
- DMI = np.stack([sio.dmi_ddict[key] for sio in self.dat])
182
- projections = np.einsum("nmi,ni->nm", u, DMI).flatten()
193
+ projections = []
194
+ for idx, sio in enumerate(self.dat):
195
+ if sio.has_dmi and key in sio.dmi_ddict:
196
+ DMI = sio.dmi_ddict[key]
197
+ else:
198
+ DMI = np.zeros(3)
199
+ proj = np.einsum("mi,i->m", u[idx], DMI)
200
+ projections.append(proj)
201
+ projections = np.concatenate(projections)
183
202
  except KeyError as err:
184
203
  raise KeyError(
185
204
  "Can not find key: %s, Please make sure the three calculations use the same k-mesh and same Rcut."
@@ -193,13 +212,14 @@ class Merger:
193
212
  # make sure that the Jani has the trace of zero
194
213
  Jdict = self.main_dat.exchange_Jdict
195
214
  Jani_dict = self.main_dat.Jani_dict
196
- for key in self.main_dat.Jani_dict.keys():
197
- Jani = self.main_dat.Jani_dict[key]
198
- shift = np.trace(Jani) / 3.0
199
- Jani_dict[key] -= shift * np.eye(3)
200
- Jdict[key] += shift
201
- self.main_dat.Jani_dict = Jani_dict
202
- self.main_dat.exchange_Jdict = Jdict
215
+ if Jani_dict is not None:
216
+ for key in Jani_dict.keys():
217
+ Jani = Jani_dict[key]
218
+ shift = np.trace(Jani) / 3.0
219
+ Jani_dict[key] -= shift * np.eye(3)
220
+ Jdict[key] += shift
221
+ self.main_dat.Jani_dict = Jani_dict
222
+ self.main_dat.exchange_Jdict = Jdict
203
223
 
204
224
 
205
225
  def merge(*paths, main_path=None, save=True, write_path="TB2J_results"):
TB2J/magnon/magnon3.py CHANGED
@@ -28,6 +28,7 @@ class MagnonParameters:
28
28
  Jiso: bool = True
29
29
  Jani: bool = False
30
30
  DMI: bool = False
31
+ SIA: bool = True
31
32
  Q: Optional[List[float]] = None
32
33
  uz_file: Optional[str] = None
33
34
  n: Optional[List[float]] = None
@@ -398,11 +399,18 @@ class Magnon:
398
399
  cell = exc.atoms.get_cell()
399
400
  pbc = exc.atoms.get_pbc()
400
401
 
402
+ # Extract SIA from kwargs, default True if not present
403
+ include_SIA = kwargs.pop("SIA", True)
404
+
405
+ JR = exc.get_full_Jtensor_for_Rlist(
406
+ order="ij33", asr=False, SIA=include_SIA, **kwargs
407
+ )
408
+
401
409
  return cls(
402
410
  nspin=exc.nspin,
403
411
  magmom=magmoms,
404
412
  Rlist=exc.Rlist,
405
- JR=exc.get_full_Jtensor_for_Rlist(order="ij33", asr=False, **kwargs),
413
+ JR=JR,
406
414
  cell=cell,
407
415
  _Q=np.zeros(3), # Default propagation vector
408
416
  _uz=np.array([[0.0, 0.0, 1.0]]), # Default quantization axis
@@ -654,7 +662,11 @@ def plot_magnon_bands_from_TB2J(
654
662
  # Load magnon calculator from TB2J results
655
663
  print(f"Loading exchange parameters from {params.path}...")
656
664
  magnon = Magnon.from_TB2J_results(
657
- path=params.path, Jiso=params.Jiso, Jani=params.Jani, DMI=params.DMI
665
+ path=params.path,
666
+ Jiso=params.Jiso,
667
+ Jani=params.Jani,
668
+ DMI=params.DMI,
669
+ SIA=params.SIA,
658
670
  )
659
671
 
660
672
  # Set reference vectors if provided
@@ -815,6 +827,18 @@ def plot_magnon_bands_cli():
815
827
  default=False,
816
828
  help="Include anisotropic exchange interactions (default: False)",
817
829
  )
830
+ parser.add_argument(
831
+ "--SIA",
832
+ action="store_true",
833
+ default=True,
834
+ help="Include single ion anisotropy (default: True)",
835
+ )
836
+ parser.add_argument(
837
+ "--no-SIA",
838
+ action="store_false",
839
+ dest="SIA",
840
+ help="Exclude single ion anisotropy",
841
+ )
818
842
  parser.add_argument(
819
843
  "-d",
820
844
  "--DMI",
@@ -881,6 +905,7 @@ def plot_magnon_bands_cli():
881
905
  filename=args.output,
882
906
  Jiso=args.Jiso,
883
907
  Jani=args.Jani,
908
+ SIA=args.SIA,
884
909
  DMI=args.DMI,
885
910
  Q=args.Q if args.Q is not None else None,
886
911
  uz_file=args.uz_file,
@@ -92,7 +92,7 @@ def rotate_spinor_matrix_einsum(M, theta, phi):
92
92
  Rotate the spinor matrix M by theta and phi,
93
93
  """
94
94
  shape = M.shape
95
- n1 = np.product(shape[:-1]) // 2
95
+ n1 = np.prod(shape[:-1]) // 2
96
96
  n2 = M.shape[-1] // 2
97
97
  Mnew = np.reshape(M, (n1, 2, n2, 2)) # .swapaxes(1, 2)
98
98
  U = rotation_matrix(theta, phi)
@@ -203,15 +203,15 @@ def test_rotate_spinor_M():
203
203
  Mrot4 = rotate_spinor_matrix_kron(M, np.pi / 2, np.pi / 2)
204
204
  Mrot5 = rotate_spinor_matrix_spkron(M, np.pi / 2, np.pi / 2)
205
205
  print(f"Rotated M with jit:\n {Mrot1}")
206
- print(f"Rotated M with einsum:\n {Mrot2-Mrot1}")
207
- print(f"Rotated M with reshape:\n {Mrot3-Mrot1}")
208
- print(f"Rotated M with kron:\n {Mrot4-Mrot1}")
209
- print(f"Rotated M with spkron:\n {Mrot5-Mrot1}")
206
+ print(f"Rotated M with einsum:\n {Mrot2 - Mrot1}")
207
+ print(f"Rotated M with reshape:\n {Mrot3 - Mrot1}")
208
+ print(f"Rotated M with kron:\n {Mrot4 - Mrot1}")
209
+ print(f"Rotated M with spkron:\n {Mrot5 - Mrot1}")
210
210
 
211
211
  M_rot00 = rotate_spinor_matrix(M, 0, 0)
212
212
  M_rot00_sph = rotate_Matrix_from_z_to_spherical(M, 0, 0)
213
- print(f"Rotated M with theta=0, phi=0 compared with M:\n {M_rot00-M}")
214
- print(f"Rotated M with theta=0, phi=0 compared with M:\n {M_rot00_sph-M}")
213
+ print(f"Rotated M with theta=0, phi=0 compared with M:\n {M_rot00 - M}")
214
+ print(f"Rotated M with theta=0, phi=0 compared with M:\n {M_rot00_sph - M}")
215
215
 
216
216
 
217
217
  def test_rotate_spinor_oneblock():
TB2J/mycfr.py CHANGED
@@ -6,20 +6,17 @@ Continued fraction representation.
6
6
  """
7
7
 
8
8
  import numpy as np
9
+ from ase.units import kB
9
10
  from scipy.linalg import eig
10
11
 
11
- kb = 8.61733e-5 # Boltzmann constant in eV
12
-
13
12
 
14
13
  class CFR:
15
- """
16
- Integration with the continued fraction representation.
17
- """
14
+ """Integration with the continued fraction representation."""
18
15
 
19
- def __init__(self, nz=50, T=60):
16
+ def __init__(self, nz: int = 50, T: float = 200.0):
20
17
  self.nz = nz
21
- self.T = 600
22
- self.beta = 1 / (kb * self.T)
18
+ self.T = float(T)
19
+ self.beta = 1.0 / (kB * self.T)
23
20
  self.Rinf = 1e10
24
21
  if nz <= 0:
25
22
  raise ValueError("nz should be larger than 0.")
@@ -27,7 +24,7 @@ class CFR:
27
24
  self.prepare_poles()
28
25
 
29
26
  def prepare_poles(self):
30
- ##b_mat = [1 / (2.0 * np.sqrt((2 * (j + 1) - 1) * (2 * (j + 1) + 1)) / (kb * self.#T)) for j in range(0, self.nz- 1)]
27
+ ##b_mat = [1 / (2.0 * np.sqrt((2 * (j + 1) - 1) * (2 * (j + 1) + 1)) / (kB * self.#T)) for j in range(0, self.nz- 1)]
31
28
  jmat = np.arange(0, self.nz - 1)
32
29
  b_mat = 1 / (2.0 * np.sqrt((2 * (jmat + 1) - 1) * (2 * (jmat + 1) + 1)))
33
30
  b_mat = np.diag(b_mat, -1) + np.diag(b_mat, 1)
@@ -42,16 +39,16 @@ class CFR:
42
39
  # np.real(self.poles) > 0, 2.0j / self.beta * residules, 0.0
43
40
  # )
44
41
 
45
- # self.path = 1j / self.poles * kb * self.T
42
+ # self.path = 1j / self.poles * kB * self.T
46
43
 
47
44
  self.path = []
48
45
  self.weights = []
49
46
  for p, r in zip(self.poles, residules):
50
47
  if p > 0:
51
- self.path.append(1j / p * kb * self.T)
48
+ self.path.append(1j / p * kB * self.T)
52
49
  w = 2.0j / self.beta * r
53
50
  self.weights.append(w)
54
- self.path.append(-1j / p * kb * self.T)
51
+ self.path.append(-1j / p * kB * self.T)
55
52
  self.weights.append(w)
56
53
 
57
54
  self.path = np.array(self.path)
@@ -61,12 +58,12 @@ class CFR:
61
58
  # A_mat = -1/2 *np.diag(1, -1) + np.diag(1, 1)
62
59
  # B_mat = np.diag([2*i-1 for i in range(1, self.nz)])
63
60
  # eigp, eigv = eig(A_mat, B_mat)
64
- # zp = 1j / eigp * kb * self.T
61
+ # zp = 1j / eigp * kB * self.T
65
62
  # Rp = 0.25 * np.diag(eigv)**2 * zp **2
66
63
 
67
64
  # print the poles and the weights
68
- for i in range(len(self.poles)):
69
- print("Pole: ", self.poles[i], "Weight: ", self.weights[i])
65
+ # for i in range(len(self.poles)):
66
+ # print("Pole: ", self.poles[i], "Weight: ", self.weights[i])
70
67
 
71
68
  # add a point to the poles: 1e10j
72
69
  self.path = np.concatenate((self.path, [self.Rinf * 1j]))
@@ -110,7 +107,10 @@ def test_cfr():
110
107
 
111
108
  r = cfr.integrate_func(test_gf, ef=2)
112
109
  r = -np.imag(r) / np.pi * 2
113
- print(r)
110
+ if not np.close(r, -1.0):
111
+ raise AssertionError(
112
+ f"Test failed, the integration should be close to -1.0 but get {r}"
113
+ )
114
114
  return r
115
115
 
116
116
 
TB2J/plot.py CHANGED
@@ -14,6 +14,32 @@ def write_eigen(qmesh, gamma=True, path="./", output_fname="EigenJq.txt", **kwar
14
14
  m.write_Jq(kmesh=qmesh, path=path, gamma=gamma, output_fname=output_fname, **kwargs)
15
15
 
16
16
 
17
+ def plot_exchange(
18
+ input_data,
19
+ ax=None,
20
+ marker="o",
21
+ fname=None,
22
+ show=False,
23
+ by_species=True,
24
+ **kwargs,
25
+ ):
26
+ """Plot exchange parameters vs distance grouped by species.
27
+ input_data: SpinIO object or path to the pickle file/directory.
28
+ """
29
+ if isinstance(input_data, str):
30
+ if os.path.isdir(input_data):
31
+ m = SpinIO.load_pickle(input_data)
32
+ else:
33
+ m = SpinIO.load_pickle(
34
+ os.path.dirname(input_data), os.path.basename(input_data)
35
+ )
36
+ else:
37
+ m = input_data
38
+ return m.plot_JvsR(
39
+ ax=ax, marker=marker, fname=fname, show=show, by_species=by_species, **kwargs
40
+ )
41
+
42
+
17
43
  def plot_magnon_band(
18
44
  fname="exchange.xml",
19
45
  path="./",