TB2J 0.9.12.9__py3-none-any.whl → 0.9.12.22__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 +78 -61
  3. TB2J/contour.py +3 -2
  4. TB2J/exchange.py +346 -51
  5. TB2J/exchangeCL2.py +285 -47
  6. TB2J/exchange_params.py +48 -0
  7. TB2J/green.py +73 -36
  8. TB2J/interfaces/abacus/gen_exchange_abacus.py +2 -1
  9. TB2J/interfaces/wannier90_interface.py +4 -4
  10. TB2J/io_exchange/__init__.py +19 -1
  11. TB2J/io_exchange/edit.py +594 -0
  12. TB2J/io_exchange/io_espins.py +276 -0
  13. TB2J/io_exchange/io_exchange.py +248 -76
  14. TB2J/io_exchange/io_tomsasd.py +4 -3
  15. TB2J/io_exchange/io_txt.py +72 -7
  16. TB2J/io_exchange/io_vampire.py +4 -2
  17. TB2J/io_merge.py +60 -40
  18. TB2J/magnon/magnon3.py +27 -2
  19. TB2J/mathutils/rotate_spin.py +7 -7
  20. TB2J/myTB.py +11 -11
  21. TB2J/mycfr.py +11 -11
  22. TB2J/pauli.py +32 -2
  23. TB2J/plot.py +26 -0
  24. TB2J/rotate_atoms.py +9 -6
  25. TB2J/scripts/TB2J_edit.py +403 -0
  26. TB2J/scripts/TB2J_plot_exchange.py +48 -0
  27. TB2J/spinham/hamiltonian.py +156 -13
  28. TB2J/spinham/hamiltonian_terms.py +40 -1
  29. TB2J/spinham/spin_xml.py +40 -8
  30. TB2J/symmetrize_J.py +140 -9
  31. TB2J/tests/test_cli_remove_sublattice.py +33 -0
  32. TB2J/tests/test_cli_toggle_exchange.py +50 -0
  33. {tb2j-0.9.12.9.dist-info → tb2j-0.9.12.22.dist-info}/METADATA +10 -7
  34. {tb2j-0.9.12.9.dist-info → tb2j-0.9.12.22.dist-info}/RECORD +38 -34
  35. {tb2j-0.9.12.9.dist-info → tb2j-0.9.12.22.dist-info}/WHEEL +1 -1
  36. {tb2j-0.9.12.9.dist-info → tb2j-0.9.12.22.dist-info}/entry_points.txt +2 -0
  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.9.dist-info → tb2j-0.9.12.22.dist-info}/licenses/LICENSE +0 -0
  40. {tb2j-0.9.12.9.dist-info → tb2j-0.9.12.22.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import sys
2
3
 
3
4
  import numpy as np
4
5
  from numpy import array_str
@@ -9,6 +10,7 @@ from TB2J.utils import symbol_number
9
10
  def write_info_section(cls, myfile):
10
11
  myfile.write("=" * 90 + "\n")
11
12
  myfile.write("Information: \n")
13
+ myfile.write(f"Executed: \n{' '.join(sys.argv)}\n")
12
14
  # myfile.write("Exchange parameters generated by TB2J %s\n" % (__version__))
13
15
  # myfile.write("Generation time: %s\n" % (now.strftime("%y/%m/%d %H:%M:%S")))
14
16
  myfile.write(cls.description)
@@ -82,6 +84,24 @@ def write_atom_section(cls, myfile):
82
84
  )
83
85
  )
84
86
 
87
+ # write single ion anisotropy
88
+ if cls.k1 is not None and cls.k1dir is not None:
89
+ myfile.write("\n")
90
+ myfile.write("-" * 90 + "\n")
91
+ myfile.write("Single Ion Anisotropy (meV): \n")
92
+ myfile.write("(k1 is the anisotropy constant, k1dir is the direction vector)\n")
93
+ myfile.write("{:^12s} {:^12s} {:^24s}\n".format("Atom number", "k1", "k1dir"))
94
+ for i, s in enumerate(symnum):
95
+ ispin = cls.index_spin[i]
96
+ if ispin >= 0:
97
+ k1 = cls.k1[ispin] * 1e3
98
+ k1dir = cls.k1dir[ispin]
99
+ myfile.write(
100
+ "{:<12s} {:12.4f} ({:7.4f}, {:7.4f}, {:7.4f})\n".format(
101
+ s, k1, k1dir[0], k1dir[1], k1dir[2]
102
+ )
103
+ )
104
+
85
105
  myfile.write("\n")
86
106
 
87
107
 
@@ -159,17 +179,17 @@ def write_exchange_section(
159
179
 
160
180
  if cls.has_biquadratic and write_experimental:
161
181
  Jprime, B = cls.biquadratic_Jdict[ll]
162
- myfile.write(f"[Testing!] Jprime: {Jprime*1e3:.3f}, B: {B*1e3:.3f}\n")
182
+ myfile.write(f"[Testing!] Jprime: {Jprime * 1e3:.3f}, B: {B * 1e3:.3f}\n")
163
183
 
164
184
  if cls.dJdx is not None:
165
185
  dJdx = cls.dJdx[ll]
166
- myfile.write(f"dJ/dx: {dJdx*1e3:.3f}\n")
186
+ myfile.write(f"dJ/dx: {dJdx * 1e3:.3f}\n")
167
187
 
168
188
  if cls.dJdx2 is not None:
169
189
  dJdx2 = cls.dJdx2[ll]
170
- myfile.write(f"d2J/dx2: {dJdx2*1e3:.3f}\n")
190
+ myfile.write(f"d2J/dx2: {dJdx2 * 1e3:.3f}\n")
171
191
 
172
- if cls.dmi_ddict is not None:
192
+ if cls.dmi_ddict is not None and ll in cls.dmi_ddict:
173
193
  DMI = cls.dmi_ddict[ll] * 1e3
174
194
  myfile.write(
175
195
  "[Testing!] DMI: ({:7.4f} {:7.4f} {:7.4f})\n".format(
@@ -188,13 +208,13 @@ def write_exchange_section(
188
208
  except Exception as e:
189
209
  myfile.write(f"[Debug!] DMI2 not available: {e}\n")
190
210
 
191
- if cls.Jani_dict is not None:
211
+ if cls.Jani_dict is not None and ll in cls.Jani_dict:
192
212
  J = cls.Jani_dict[ll] * 1e3
193
213
  myfile.write(
194
214
  f"[Testing!]J_ani:\n{array_str(J, precision=3, suppress_small=True)}\n"
195
215
  )
196
216
 
197
- if cls.NJT_ddict is not None:
217
+ if cls.NJT_ddict is not None and ll in cls.NJT_ddict:
198
218
  DMI = cls.NJT_ddict[ll] * 1e3
199
219
  myfile.write(
200
220
  "[Experimental!] DMI_NJt: ({:7.4f} {:7.4f} {:7.4f})\n".format(
@@ -202,7 +222,7 @@ def write_exchange_section(
202
222
  )
203
223
  )
204
224
 
205
- if cls.NJT_Jdict is not None:
225
+ if cls.NJT_Jdict is not None and ll in cls.NJT_Jdict:
206
226
  J = cls.NJT_Jdict[ll] * 1e3
207
227
  myfile.write(
208
228
  "[Testing!] Jani_NJt: ({:7.4f} {:7.4f} {:7.4f})\n".format(
@@ -305,6 +325,50 @@ def write_Jq_info(cls, kpts, evals, evecs, myfile, special_kpoints={}):
305
325
  print(f"{sns[cls.ind_atoms[i]]}, {v[0]: 8.3f}, {v[2]: 8.3f}, {v[2]: 8.3f}\n")
306
326
 
307
327
 
328
+ def write_sia_section(cls, myfile):
329
+ """
330
+ Write single ion anisotropy tensor section.
331
+ """
332
+ if cls.has_sia_tensor and cls.sia_tensor is not None:
333
+ myfile.write("\n")
334
+ myfile.write("=" * 90 + "\n")
335
+ myfile.write("Single Ion Anisotropy: \n")
336
+ symnum = symbol_number(cls.atoms)
337
+ sns = list(symnum.keys())
338
+ myfile.write(
339
+ "{:^12s} {:^13s} {:^13s} {:^13s}\n".format(
340
+ "Atom_number", "A_xx", "A_xy", "A_xz"
341
+ )
342
+ )
343
+ myfile.write(
344
+ "{:^12s} {:^13s} {:^13s} {:^13s}\n".format("", "A_yx", "A_yy", "A_yz")
345
+ )
346
+ myfile.write(
347
+ "{:^12s} {:^13s} {:^13s} {:^13s}\n".format("", "A_zx", "A_zy", "A_zz")
348
+ )
349
+ for i, tensor in cls.sia_tensor.items():
350
+ iatom = cls.ind_atoms[i]
351
+ myfile.write(
352
+ "{:<12s} {:13.8f} {:13.8f} {:13.8f}\n".format(
353
+ sns[iatom],
354
+ tensor[0, 0] * 1e3,
355
+ tensor[0, 1] * 1e3,
356
+ tensor[0, 2] * 1e3,
357
+ )
358
+ )
359
+ myfile.write(
360
+ "{:<12s} {:13.8f} {:13.8f} {:13.8f}\n".format(
361
+ "", tensor[1, 0] * 1e3, tensor[1, 1] * 1e3, tensor[1, 2] * 1e3
362
+ )
363
+ )
364
+ myfile.write(
365
+ "{:<12s} {:13.8f} {:13.8f} {:13.8f}\n".format(
366
+ "", tensor[2, 0] * 1e3, tensor[2, 1] * 1e3, tensor[2, 2] * 1e3
367
+ )
368
+ )
369
+ myfile.write("-" * 88 + "\n")
370
+
371
+
308
372
  def write_txt(
309
373
  cls,
310
374
  path="TB2J_results",
@@ -321,6 +385,7 @@ def write_txt(
321
385
  write_info_section(cls, myfile)
322
386
  write_atom_section(cls, myfile)
323
387
  write_orbital_section(cls, myfile)
388
+ write_sia_section(cls, myfile)
324
389
  write_exchange_section(
325
390
  cls,
326
391
  myfile,
@@ -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} "
@@ -66,7 +66,7 @@ def write_vampire_unitcell_file(cls, fname):
66
66
 
67
67
  def write_vampire_mat_file(cls, fname):
68
68
  mat_tmpl = """#---------------------------------------------------
69
- # Material {id}
69
+ # Material {id}
70
70
  #---------------------------------------------------
71
71
  material[{id}]:material-name={name}
72
72
  material[{id}]:damping-constant={damping}
@@ -75,6 +75,8 @@ material[{id}]:uniaxial-anisotropy-constant={k1}
75
75
  material[{id}]:material-element={name}
76
76
  material[{id}]:initial-spin-direction = {spinat}
77
77
  material[{id}]:uniaxial-anisotropy-direction = {k1dir}
78
+ # The following line is required for vampire 5 and later.
79
+ material[{id}]:unit-cell-category = {id}
78
80
  #---------------------------------------------------
79
81
  """
80
82
  with open(fname, "w") as myfile:
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/myTB.py CHANGED
@@ -1,15 +1,17 @@
1
- import os
2
- import numpy as np
3
1
  import copy
4
- from scipy.linalg import eigh
5
- from scipy.sparse import csr_matrix
6
- from scipy.io import netcdf_file
2
+ import os
7
3
  from collections import defaultdict
8
4
 
5
+ import numpy as np
6
+
9
7
  # from tbmodels import Model
10
8
  from ase.atoms import Atoms
9
+ from scipy.io import netcdf_file
10
+ from scipy.linalg import eigh
11
+ from scipy.sparse import csr_matrix
12
+
11
13
  from TB2J.utils import auto_assign_basis_name
12
- from TB2J.wannier import parse_ham, parse_xyz, parse_atoms, parse_tb
14
+ from TB2J.wannier import parse_atoms, parse_ham, parse_tb, parse_xyz
13
15
 
14
16
 
15
17
  class AbstractTB:
@@ -187,9 +189,7 @@ class MyTB(AbstractTB):
187
189
  if os.path.exists(tb_fname):
188
190
  xcart, nbasis, data, R_degens = parse_tb(fname=tb_fname)
189
191
  else:
190
- nbasis, data, R_degens = parse_ham(
191
- fname=os.path.join(path, prefix + "_hr.dat")
192
- )
192
+ nbasis, data, R_degens = parse_ham(fname=hr_fname)
193
193
  xcart, _, _ = parse_xyz(fname=os.path.join(path, prefix + "_centres.xyz"))
194
194
 
195
195
  if atoms is None:
@@ -237,7 +237,7 @@ class MyTB(AbstractTB):
237
237
  return m
238
238
 
239
239
  def gen_ham(self, k, convention=2):
240
- """
240
+ r"""
241
241
  generate hamiltonian matrix at k point.
242
242
  H_k( i, j)=\sum_R H_R(i, j)^phase.
243
243
  There are two conventions,
@@ -451,7 +451,7 @@ class MyTB(AbstractTB):
451
451
  nbasis = root.dimensions["nbasis"]
452
452
  nspin = root.dimensions["nspin"]
453
453
  ndim = root.dimensions["ndim"]
454
- natom = root.dimensions["natom"]
454
+ _natom = root.dimensions["natom"]
455
455
  Rlist = root.variables["R"][:]
456
456
  mdata_real = root.variables["data_real"][:]
457
457
  mdata_imag = root.variables["data_imag"][:]
TB2J/mycfr.py CHANGED
@@ -5,21 +5,18 @@
5
5
  Continued fraction representation.
6
6
  """
7
7
 
8
+ import ase.units.kB as kb
8
9
  import numpy as np
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.")
@@ -65,8 +62,8 @@ class CFR:
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/pauli.py CHANGED
@@ -59,7 +59,7 @@ def pauli_decomp2(M):
59
59
  )
60
60
 
61
61
 
62
- def pauli_sigma_norm(M):
62
+ def pauli_sigma_norm_old(M):
63
63
  MI, Mx, My, Mz = pauli_decomp2(M)
64
64
  return np.linalg.norm([Mx, My, Mz])
65
65
 
@@ -134,7 +134,7 @@ def pauli_block(M, idim):
134
134
  return tmp
135
135
 
136
136
 
137
- def pauli_block_all(M):
137
+ def pauli_block_all_old(M):
138
138
  MI = (M[::2, ::2] + M[1::2, 1::2]) / 2.0
139
139
  Mx = (M[::2, 1::2] + M[1::2, ::2]) / 2.0
140
140
  # Note that this is not element wise product with sigma_y but dot product
@@ -143,6 +143,28 @@ def pauli_block_all(M):
143
143
  return MI, Mx, My, Mz
144
144
 
145
145
 
146
+ def pauli_block_all(array):
147
+ A00 = array[..., ::2, ::2]
148
+ A01 = array[..., ::2, 1::2]
149
+ A10 = array[..., 1::2, ::2]
150
+ A11 = array[..., 1::2, 1::2]
151
+ n2 = array.shape[-1] // 2
152
+
153
+ out_dtype = np.result_type(array.dtype, np.complex64)
154
+ block = np.empty((*array.shape[:-2], 4, n2, n2), dtype=out_dtype)
155
+
156
+ np.add(A00, A11, out=block[..., 0, :, :])
157
+ block[..., 0, :, :] *= 0.5
158
+ np.add(A01, A10, out=block[..., 1, :, :])
159
+ block[..., 1, :, :] *= 0.5
160
+ np.subtract(A01, A10, out=block[..., 2, :, :])
161
+ block[..., 2, :, :] *= 0.5j
162
+ np.subtract(A00, A11, out=block[..., 3, :, :])
163
+ block[..., 3, :, :] *= 0.5
164
+
165
+ return block
166
+
167
+
146
168
  def gather_pauli_blocks(MI, Mx, My, Mz, coeffs=[1.0, 1.0, 1.0, 1.0]):
147
169
  """
148
170
  Gather the I, x, y, z component of a matrix.
@@ -212,3 +234,11 @@ def pauli_block_sigma_norm(M):
212
234
  evec = np.array((ex, ey, ez))
213
235
  evec = evec / np.linalg.norm(evec)
214
236
  return Mx * evec[0] + My * evec[1] + Mz * evec[2]
237
+
238
+
239
+ def pauli_sigma_norm(array):
240
+ block = pauli_block_all(array)[..., 1:, :, :]
241
+ E = np.trace(block, axis1=-2, axis2=-1)
242
+ E /= np.linalg.norm(E, axis=-1, keepdims=True)
243
+ np.multiply(block, E[..., None, None], out=block)
244
+ return block.sum(axis=-3)
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="./",
TB2J/rotate_atoms.py CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  import copy
3
- from ase.io import read, write
3
+
4
4
  import numpy as np
5
+ from ase.io import read, write
5
6
  from TB2J.tensor_rotate import Rzx, Rzy, Rzz
6
7
 
7
8
 
@@ -15,14 +16,16 @@ def rotate_atom_xyz(atoms, noncollinear=False):
15
16
  will be generated.
16
17
  """
17
18
 
19
+ yield atoms
18
20
  rotation_axes = [(1, 0, 0), (0, 1, 0)]
19
21
  if noncollinear:
20
22
  rotation_axes += [(1, 1, 0), (1, 0, 1), (0, 1, 1)]
21
-
23
+
22
24
  for axis in rotation_axes:
23
25
  rotated_atoms = copy.deepcopy(atoms)
24
26
  rotated_atoms.rotate(90, axis, rotate_cell=True)
25
27
  yield rotated_atoms
28
+ yield atoms
26
29
 
27
30
 
28
31
  def rotate_atom_spin_one_rotation(atoms, Rotation):
@@ -109,7 +112,7 @@ def rotate_xyz(fname, ftype="xyz", noncollinear=False):
109
112
  rotated = rotate_atom_xyz(atoms, noncollinear=noncollinear)
110
113
 
111
114
  for i, rotated_atoms in enumerate(rotated):
112
- write(f"atoms_{i+1}.{ftype}", rotated_atoms)
113
- write(f"atoms_0.{ftype}", atoms)
114
-
115
- print(f"The output has been written to the atoms_i.{ftype} files. atoms_0.{ftype} contains the reference structure.")
115
+ write(f"atoms_{i}.{ftype}", rotated_atoms)
116
+ print(
117
+ f"The output has been written to the atoms_i.{ftype} files. atoms_0.{ftype} contains the reference structure."
118
+ )