TB2J 0.9.9rc5__py3-none-any.whl → 0.9.9rc7__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 (33) hide show
  1. TB2J/Jdownfolder.py +11 -9
  2. TB2J/MAEGreen.py +123 -19
  3. TB2J/anisotropy.py +104 -30
  4. TB2J/contour.py +16 -0
  5. TB2J/exchange.py +14 -17
  6. TB2J/exchangeCL2.py +20 -11
  7. TB2J/exchange_params.py +8 -0
  8. TB2J/green.py +15 -1
  9. TB2J/interfaces/siesta_interface.py +2 -1
  10. TB2J/io_exchange/io_exchange.py +44 -5
  11. TB2J/io_exchange/io_matjes.py +225 -0
  12. TB2J/kpoints.py +15 -1
  13. TB2J/mathutils/fibonacci_sphere.py +74 -0
  14. TB2J/mycfr.py +114 -0
  15. TB2J/orbital_magmom.py +36 -0
  16. TB2J/orbmap.py +4 -4
  17. TB2J/symmetrize_J.py +24 -11
  18. {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info}/METADATA +16 -7
  19. {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info}/RECORD +33 -29
  20. {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info}/WHEEL +1 -1
  21. {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_downfold.py +0 -0
  22. {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_eigen.py +0 -0
  23. {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_magnon.py +0 -0
  24. {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_magnon_dos.py +0 -0
  25. {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_merge.py +0 -0
  26. {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_rotate.py +0 -0
  27. {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_rotateDM.py +0 -0
  28. {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/abacus2J.py +0 -0
  29. {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/siesta2J.py +0 -0
  30. {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/wann2J.py +0 -0
  31. {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info}/entry_points.txt +0 -0
  32. {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info/licenses}/LICENSE +0 -0
  33. {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info}/top_level.txt +0 -0
TB2J/exchange.py CHANGED
@@ -10,13 +10,12 @@ from TB2J.exchange_params import ExchangeParams
10
10
  from TB2J.external import p_map
11
11
  from TB2J.green import TBGreen
12
12
  from TB2J.io_exchange import SpinIO
13
+ from TB2J.mycfr import CFR
13
14
  from TB2J.orbmap import map_orbs_matrix
14
15
  from TB2J.pauli import pauli_block_all, pauli_block_sigma_norm
15
16
  from TB2J.utils import (
16
17
  kmesh_to_R,
17
- simpson_nonuniform,
18
18
  symbol_number,
19
- trapezoidal_nonuniform,
20
19
  )
21
20
 
22
21
 
@@ -28,6 +27,7 @@ class Exchange(ExchangeParams):
28
27
  self._prepare_Rlist()
29
28
  self.set_tbmodels(tbmodels)
30
29
  self._adjust_emin()
30
+ # self._prepare_elist(method="CFR")
31
31
  self._prepare_elist(method="legendre")
32
32
  self._prepare_basis()
33
33
  self._prepare_orb_dict()
@@ -45,8 +45,9 @@ class Exchange(ExchangeParams):
45
45
  os.makedirs(self.orbpath, exist_ok=True)
46
46
 
47
47
  def _adjust_emin(self):
48
- self.emin = self.G.find_energy_ingap(rbound=self.efermi - 10.0) - self.efermi
49
- # self.emin = -22.0
48
+ self.emin = self.G.find_energy_ingap(rbound=self.efermi - 15.0) - self.efermi
49
+ # self.emin = self.G.find_energy_ingap(rbound=self.efermi - 15.0) - self.efermi
50
+ # self.emin = -42.0
50
51
  print(f"A gap is found at {self.emin}, set emin to it.")
51
52
 
52
53
  def set_tbmodels(self, tbmodels):
@@ -61,21 +62,24 @@ class Exchange(ExchangeParams):
61
62
  for k in kmesh:
62
63
  self.kmesh = list(map(lambda x: x // 2 * 2 + 1, kmesh))
63
64
 
64
- def _prepare_elist(self, method="legendre"):
65
+ def _prepare_elist(self, method="CFR"):
65
66
  """
66
67
  prepare list of energy for integration.
67
68
  The path has three segments:
68
69
  emin --1-> emin + 1j*height --2-> emax+1j*height --3-> emax
69
70
  """
70
- self.contour = Contour(self.emin, self.emax)
71
71
  # if method.lower() == "rectangle":
72
72
  # self.contour.build_path_rectangle(
73
73
  # height=self.height, nz1=self.nz1, nz2=self.nz2, nz3=self.nz3
74
74
  # )
75
75
  if method.lower() == "semicircle":
76
+ self.contour = Contour(self.emin, self.emax)
76
77
  self.contour.build_path_semicircle(npoints=self.nz, endpoint=True)
77
78
  elif method.lower() == "legendre":
79
+ self.contour = Contour(self.emin, self.emax)
78
80
  self.contour.build_path_legendre(npoints=self.nz, endpoint=True)
81
+ elif method.lower() == "cfr":
82
+ self.contour = CFR(nz=self.nz, T=600)
79
83
  else:
80
84
  raise ValueError(f"The path cannot be of type {method}.")
81
85
 
@@ -114,6 +118,7 @@ class Exchange(ExchangeParams):
114
118
  else:
115
119
  try:
116
120
  atom_sym, orb_sym = base[:2]
121
+ iatom = sdict[atom_sym]
117
122
  except Exception:
118
123
  iatom = base.iatom
119
124
  atom_sym = base.iatom
@@ -168,7 +173,6 @@ or badly localized. Please check the Wannier centers in the Wannier90 output fil
168
173
  self.mmats = {}
169
174
  self.orbital_names = {}
170
175
  self.norb_reduced = {}
171
- print(f"self.backend_name: {self.backend_name}")
172
176
  if self.backend_name.upper() in ["SIESTA", "ABACUS", "LCAOHAMILTONIAN"]:
173
177
  # print(f"magntic_elements: {self.magnetic_elements}")
174
178
  # print(f"include_orbs: {self.include_orbs}")
@@ -562,20 +566,13 @@ class ExchangeNCL(Exchange):
562
566
  AijRs: a list of AijR,
563
567
  wherer AijR: array of ((nR, n, n, 4,4), dtype=complex)
564
568
  """
565
- if method == "trapezoidal":
566
- integrate = trapezoidal_nonuniform
567
- elif method == "simpson":
568
- integrate = simpson_nonuniform
569
-
570
- # self.rho = integrate(self.contour.path, rhoRs)
571
569
  for iR, R in enumerate(self.R_ijatom_dict):
572
570
  for iatom, jatom in self.R_ijatom_dict[R]:
573
571
  f = AijRs[(R, iatom, jatom)]
574
- self.A_ijR[(R, iatom, jatom)] = integrate(self.contour.path, f)
572
+ self.A_ijR[(R, iatom, jatom)] = self.contour.integrate_values(f)
573
+
575
574
  if self.orb_decomposition:
576
- self.A_ijR_orb[(R, iatom, jatom)] = integrate(
577
- self.contour.path, AijRs_orb[(R, iatom, jatom)]
578
- )
575
+ self.contour.integrate_values(AijRs_orb[(R, iatom, jatom)])
579
576
 
580
577
  def get_quantities_per_e(self, e):
581
578
  Gk_all = self.G.get_Gk_all(e)
TB2J/exchangeCL2.py CHANGED
@@ -1,15 +1,18 @@
1
1
  """
2
2
  Exchange from Green's function
3
3
  """
4
+
4
5
  import os
5
6
  from collections import defaultdict
7
+
6
8
  import numpy as np
7
- from TB2J.green import TBGreen
8
- from TB2J.io_exchange import SpinIO
9
9
  from tqdm import tqdm
10
+
10
11
  from TB2J.external import p_map
12
+ from TB2J.green import TBGreen
13
+ from TB2J.io_exchange import SpinIO
14
+
11
15
  from .exchange import ExchangeCL
12
- from .utils import simpson_nonuniform, trapezoidal_nonuniform
13
16
 
14
17
 
15
18
  class ExchangeCL2(ExchangeCL):
@@ -221,17 +224,23 @@ class ExchangeCL2(ExchangeCL):
221
224
  # shutil.rmtree(path)
222
225
 
223
226
  def integrate(self, method="simpson"):
224
- if method == "trapezoidal":
225
- integrate = trapezoidal_nonuniform
226
- elif method == "simpson":
227
- integrate = simpson_nonuniform
227
+ # if method == "trapezoidal":
228
+ # integrate = trapezoidal_nonuniform
229
+ # elif method == "simpson":
230
+ # integrate = simpson_nonuniform
228
231
  for R, ijpairs in self.R_ijatom_dict.items():
229
232
  for iatom, jatom in ijpairs:
230
- self.Jorb[(R, iatom, jatom)] = integrate(
231
- self.contour.path, self.Jorb_list[(R, iatom, jatom)]
233
+ # self.Jorb[(R, iatom, jatom)] = integrate(
234
+ # self.contour.path, self.Jorb_list[(R, iatom, jatom)]
235
+ # )
236
+ # self.JJ[(R, iatom, jatom)] = integrate(
237
+ # self.contour.path, self.JJ_list[(R, iatom, jatom)]
238
+ # )
239
+ self.Jorb[(R, iatom, jatom)] = self.contour.integrate_values(
240
+ self.Jorb_list[(R, iatom, jatom)]
232
241
  )
233
- self.JJ[(R, iatom, jatom)] = integrate(
234
- self.contour.path, self.JJ_list[(R, iatom, jatom)]
242
+ self.JJ[(R, iatom, jatom)] = self.contour.integrate_values(
243
+ self.JJ_list[(R, iatom, jatom)]
235
244
  )
236
245
 
237
246
  def get_quantities_per_e(self, e):
TB2J/exchange_params.py CHANGED
@@ -115,6 +115,14 @@ def add_exchange_args_to_parser(parser: argparse.ArgumentParser):
115
115
  type=str,
116
116
  nargs="*",
117
117
  )
118
+
119
+ parser.add_argument(
120
+ "--spinor",
121
+ help="whether the Wannier functions are spinor. Default: False",
122
+ action="store_true",
123
+ default=False,
124
+ )
125
+
118
126
  parser.add_argument(
119
127
  "--rcut",
120
128
  help="cutoff of spin pair distance. The default is to calculate all commensurate R point to the k mesh.",
TB2J/green.py CHANGED
@@ -18,6 +18,17 @@ from TB2J.kpoints import ir_kpts, monkhorst_pack
18
18
  MAX_EXP_ARGUMENT = np.log(sys.float_info.max)
19
19
 
20
20
 
21
+ def eigen_to_G2(H, S, efermi, energy):
22
+ """calculate green's function from eigenvalue/eigenvector for energy(e-ef): G(e-ef).
23
+ :param H: Hamiltonian matrix in eigenbasis
24
+ :param S: Overlap matrix in eigenbasis
25
+ :param efermi: fermi energy
26
+ :param energy: energy level e - efermi
27
+ """
28
+ # G = ((E+Ef) S - H)^-1
29
+ return np.linalg.inv((energy + efermi) * S - H)
30
+
31
+
21
32
  def eigen_to_G(evals, evecs, efermi, energy):
22
33
  """calculate green's function from eigenvalue/eigenvector for energy(e-ef): G(e-ef).
23
34
  :param evals: eigen values
@@ -121,7 +132,10 @@ class TBGreen:
121
132
  elif kmesh is not None:
122
133
  if ibz:
123
134
  self.kpts, self.kweights = ir_kpts(
124
- atoms=tbmodel.atoms, mp_grid=kmesh, ir=True, is_time_reversal=False
135
+ atoms=tbmodel.atoms,
136
+ mp_grid=kmesh,
137
+ ir=True,
138
+ is_time_reversal=False,
125
139
  )
126
140
  self.nkpts = len(self.kpts)
127
141
  print(f"Using IBZ of kmesh of {kmesh}")
@@ -156,6 +156,7 @@ Warning: The DMI component parallel to the spin orientation, the Jani which has
156
156
  atoms=model.atoms,
157
157
  basis=basis,
158
158
  efermi=None,
159
+ angles="miller",
159
160
  # magnetic_elements=magnetic_elements,
160
161
  # include_orbs=include_orbs,
161
162
  **exargs,
@@ -163,7 +164,7 @@ Warning: The DMI component parallel to the spin orientation, the Jani which has
163
164
  # thetas = [0, np.pi / 2, np.pi, 3 * np.pi / 2]
164
165
  # phis = [0, 0, 0, 0]
165
166
  # MAE.set_angles(thetas=thetas, phis=phis)
166
- MAE.run(output_path=f"{output_path}_anisotropy")
167
+ MAE.run(output_path=f"{output_path}_anisotropy", with_eigen=False)
167
168
  print(
168
169
  f"MAE calculation finished. The results are in {output_path} directory."
169
170
  )
@@ -13,6 +13,11 @@ import pickle
13
13
  from collections.abc import Iterable
14
14
  from datetime import datetime
15
15
 
16
+ import matplotlib
17
+
18
+ matplotlib.use("Agg")
19
+ import gc
20
+
16
21
  import matplotlib.pyplot as plt
17
22
  import numpy as np
18
23
 
@@ -320,7 +325,11 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
320
325
  i = self.i_spin(i)
321
326
  j = self.i_spin(j)
322
327
  if iso_only:
323
- Jtensor = np.eye(3) * self.get_J(i, j, R)
328
+ J = self.get_Jiso(i, j, R)
329
+ if J is not None:
330
+ Jtensor = np.eye(3) * self.get_J(i, j, R)
331
+ else:
332
+ Jtensor = np.eye(3) * 0
324
333
  else:
325
334
  Jtensor = combine_J_tensor(
326
335
  Jiso=self.get_J(i, j, R),
@@ -329,7 +338,13 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
329
338
  )
330
339
  return Jtensor
331
340
 
332
- def get_full_Jtensor_for_one_R(self, R):
341
+ def get_J_tensor_dict(self):
342
+ Jdict = {}
343
+ for i, j, R in self.ijR_list():
344
+ Jdict[(i, j, R)] = self.get_J_tensor(i, j, R)
345
+ return Jdict
346
+
347
+ def get_full_Jtensor_for_one_R(self, R, iso_only=False):
333
348
  """
334
349
  Return the full exchange tensor of all i and j for cell R.
335
350
  param R (tuple of integers): cell index R
@@ -340,15 +355,17 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
340
355
  Jmat = np.zeros((n3, n3), dtype=float)
341
356
  for i in range(self.nspin):
342
357
  for j in range(self.nspin):
343
- Jmat[i * 3 : i * 3 + 3, j * 3 : j * 3 + 3] = self.get_J_tensor(i, j, R)
358
+ Jmat[i * 3 : i * 3 + 3, j * 3 : j * 3 + 3] = self.get_J_tensor(
359
+ i, j, R, iso_only=iso_only
360
+ )
344
361
  return Jmat
345
362
 
346
- def get_full_Jtensor_for_Rlist(self, asr=False, iso_only=False):
363
+ def get_full_Jtensor_for_Rlist(self, asr=False, iso_only=True):
347
364
  n3 = self.nspin * 3
348
365
  nR = len(self.Rlist)
349
366
  Jmat = np.zeros((nR, n3, n3), dtype=float)
350
367
  for iR, R in enumerate(self.Rlist):
351
- Jmat[iR] = self.get_full_Jtensor_for_one_R(R)
368
+ Jmat[iR] = self.get_full_Jtensor_for_one_R(R, iso_only=iso_only)
352
369
  if asr:
353
370
  iR0 = np.argmin(np.linalg.norm(self.Rlist, axis=1))
354
371
  assert np.linalg.norm(self.Rlist[iR0]) == 0
@@ -394,6 +411,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
394
411
  self.write_multibinit(path=os.path.join(path, "Multibinit"))
395
412
  self.write_tom_format(path=os.path.join(path, "TomASD"))
396
413
  self.write_vampire(path=os.path.join(path, "Vampire"))
414
+ self.write_matjes(path=os.path.join(path, "Matjes"))
397
415
 
398
416
  self.plot_all(savefile=os.path.join(path, "JvsR.pdf"))
399
417
  # self.write_Jq(kmesh=[9, 9, 9], path=path)
@@ -544,6 +562,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
544
562
  plt.show()
545
563
  plt.clf()
546
564
  plt.close()
565
+ gc.collect() # This is to fix the tk error if multiprocess is used.
547
566
  return fig, axes
548
567
 
549
568
  def write_tom_format(self, path):
@@ -561,6 +580,11 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
561
580
 
562
581
  write_uppasd(self, path=path)
563
582
 
583
+ def write_matjes(self, path):
584
+ from TB2J.io_exchange.io_matjes import write_matjes
585
+
586
+ write_matjes(self, path=path)
587
+
564
588
 
565
589
  def gen_distance_dict(ind_mag_atoms, atoms, Rlist):
566
590
  distance_dict = {}
@@ -576,6 +600,21 @@ def gen_distance_dict(ind_mag_atoms, atoms, Rlist):
576
600
  return distance_dict
577
601
 
578
602
 
603
+ def get_ind_shell(distance_dict, symprec=1e-5):
604
+ """
605
+ return a dictionary of shell index for each pair of atoms.
606
+ The index of shell is the ith shortest distances between all magnetic atom pairs.
607
+ """
608
+ shell_dict = {}
609
+ distances = np.array([x[1] for x in distance_dict.values()])
610
+ distances_int = np.round(distances / symprec).astype(int)
611
+ dint = sorted(np.unique(distances_int))
612
+ dintdict = dict(zip(dint, range(len(dint))))
613
+ for key, val in enumerate(distances_int):
614
+ shell_dict[key] = dintdict[val]
615
+ return shell_dict
616
+
617
+
579
618
  def test_spin_io():
580
619
  import numpy as np
581
620
  from ase import Atoms
@@ -0,0 +1,225 @@
1
+ import os
2
+
3
+ import numpy as np
4
+
5
+ from TB2J.utils import symbol_number
6
+ from ase.units import Bohr, nm
7
+
8
+
9
+ def get_ind_shell(distance_dict, symprec=1e-5):
10
+ """
11
+ return a dictionary of shell index for each pair of atoms.
12
+ The index of shell is the ith shortest distances between all magnetic atom pairs.
13
+ """
14
+ shell_dict = {}
15
+ distances = np.array([x[1] for x in distance_dict.values()])
16
+ distances_int = np.round(distances / symprec).astype(int)
17
+ dint = sorted(np.unique(distances_int))
18
+ dintdict = dict(zip(dint, range(len(dint))))
19
+ for key, val in distance_dict.items():
20
+ di = np.round(val[1] / symprec).astype(int)
21
+ shell_dict[key] = dintdict[di]
22
+ return shell_dict
23
+
24
+
25
+ def _write_symmetry(cls, path, fname="symmetry.in", symmetry=True):
26
+ fname = os.path.join(path, fname)
27
+ if not symmetry:
28
+ with open(fname, "w") as myfile:
29
+ myfile.write("1 \n")
30
+ myfile.write("\n")
31
+ myfile.write("! identity operation\n")
32
+ myfile.write("1.0000000000 0.0000000000 0.0000000000\n")
33
+ myfile.write("0.0000000000 1.0000000000 0.0000000000\n")
34
+ myfile.write("0.0000000000 0.0000000000 1.0000000000\n")
35
+ myfile.write("0.0000000000 0.0000000000 0.0000000000\n")
36
+ else:
37
+ raise NotImplementedError("Symmetry not implemented yet")
38
+
39
+
40
+ def write_matjes(cls, path="TB2J_results/Matjes", symmetry=False):
41
+ if not os.path.exists(path):
42
+ os.makedirs(path)
43
+ inputfname = os.path.join(path, "input")
44
+ with open(inputfname, "w") as myfile:
45
+ _write_lattice_supercell(cls, myfile)
46
+ _write_atoms(cls, myfile)
47
+ _write_magnetic_interactions(cls, myfile)
48
+ #_write_isotropic_exchange(cls, myfile, symmetry=symmetry)
49
+ _write_magnetic_anisotropy(cls, myfile)
50
+ # _write_dmi(cls, myfile)
51
+ _write_exchange_tensor(cls, myfile, symmetry=symmetry)
52
+ _write_parameters(cls, myfile, symmetry=symmetry)
53
+ print(f"writting symmetries ")
54
+ _write_symmetry(cls, path, fname="symmetries.in", symmetry=False)
55
+
56
+ def _write_parameters(cls, myfile, symmetry=False):
57
+ if symmetry:
58
+ myfile.write("cal_sym T\n")
59
+ myfile.write("sym_mode 2\n")
60
+ else:
61
+ myfile.write("cal_sym F \n")
62
+ myfile.write("sym_mode 0\n")
63
+
64
+ def _write_lattice_supercell(cls, myfile):
65
+ myfile.write("# Lattice and supercell\n")
66
+ myfile.write(
67
+ "Periodic_log .T. .T. .T. # periodic boundary conditions along vector 1, 2 and 3\n"
68
+ )
69
+ myfile.write("Nsize 8 8 8 # size of the supercell\n")
70
+ try:
71
+ unitcell = cls.atoms.get_cell().reshape((3, 3))
72
+ except Exception:
73
+ unitcell = cls.atoms.get_cell().array.reshape((3, 3))
74
+ uc_lengths = np.linalg.norm(unitcell, axis=1)
75
+ unitcell /= uc_lengths[:, None]
76
+ myfile.write(f"alat {uc_lengths[0]/nm} {uc_lengths[1]/nm} {uc_lengths[2]/nm} #lattice constant lengths\n")
77
+ myfile.write(
78
+ "lattice #lattice vector should be orthogonal or expressed in cartesian\n")
79
+ myfile.write(
80
+ f"{unitcell[0][0]} {unitcell[0][1]} {unitcell[0][2]} # a_11 a_12 a_1 first lattice vector in line (does not need to be normalize)\n"
81
+ )
82
+ myfile.write(
83
+ f"{unitcell[1][0]} {unitcell[1][1]} {unitcell[1][2]} # a_21 a_22 a_23 second lattice vector in line (does not need to be normalize)\n"
84
+ )
85
+ myfile.write(
86
+ f"{unitcell[2][0]} {unitcell[2][1]} {unitcell[2][2]} # a_31 a_32 a_33 third lattice vector in line\n"
87
+ )
88
+
89
+
90
+ def get_atoms_info(atoms, spinat, symmetry=False):
91
+ if symmetry:
92
+ raise NotImplementedError("Symmetry not implemented yet")
93
+ else:
94
+ symnum = symbol_number(atoms)
95
+ atom_types = list(symnum.keys())
96
+ magmoms = np.linalg.norm(spinat, axis=1)
97
+ masses = atoms.get_masses()
98
+ tags = [i for i in range(len(atom_types))]
99
+ return atom_types, magmoms, masses, tags
100
+
101
+
102
+ def _write_atoms(cls, myfile, symmetry=False):
103
+ myfile.write("\n")
104
+ myfile.write("# Atoms\n")
105
+ atom_types, magmoms, masses, tags = get_atoms_info(
106
+ cls.atoms, cls.spinat, symmetry=symmetry
107
+ )
108
+
109
+ myfile.write(f"atomtypes {len(atom_types)} #Number of types atom\n")
110
+ for i, atom_type in enumerate(atom_types):
111
+ m = magmoms[i]
112
+ mass = masses[i]
113
+ charge = 0
114
+ myfile.write(
115
+ f"{atom_type} {m} {mass} 0.0 F 0 # atom type: (name, mag. moment, mass, charge, displacement, number TB-orb.)\n"
116
+ )
117
+
118
+ myfile.write(
119
+ f"\natoms {len(cls.atoms)} positions of the atom in the unit cell\n"
120
+ )
121
+ for i, atom in enumerate(cls.atoms):
122
+ spos = cls.atoms.get_scaled_positions()[i]
123
+ myfile.write(f"{atom_types[tags[i]]} {spos[0]}, {spos[1]}, {spos[2]} \n")
124
+
125
+
126
+ def _write_magnetic_interactions(cls, myfile, symmetry=False):
127
+ myfile.write("\nThe Hamiltonian\n")
128
+ myfile.write("# Magnetic interactions\n")
129
+
130
+
131
+ def _write_isotropic_exchange(cls, myfile, symmetry=False):
132
+ myfile.write("\nmagnetic_J\n")
133
+ shell_dict = get_ind_shell(cls.distance_dict)
134
+ if symmetry:
135
+ Jdict = cls.reduced_exchange_Jdict
136
+ else:
137
+ Jdict = cls.exchange_Jdict
138
+ for key, val in Jdict.items():
139
+ R, i, j = key
140
+ ishell = shell_dict[(R, i, j)]
141
+ myfile.write(
142
+ f"{i+1} {j+1} {ishell} {val} # between atoms type {i+1} and {j+1}, shell {R}, amplitude in eV \n"
143
+ )
144
+ myfile.write(
145
+ "\nc_H_J -1 apply 1/2 in front of the sum of the exchange energy - default is -1\n"
146
+ )
147
+
148
+
149
+ def _write_magnetic_anisotropy(cls, myfile):
150
+ if cls.k1 is None:
151
+ return
152
+ else:
153
+ myfile.write("\nmagnetic_anisotropy \n")
154
+ for i, k1 in enumerate(cls.k1):
155
+ myfile.write(
156
+ f"{i+1} {cls.k1dir[i][0]} {cls.k1dir[i][1]} {cls.k1dir[i][2]} {k1} anisotropy of atoms type {i+1}, direction {cls.k1dir[i][0]} {cls.k1dir[i][1]} {cls.k1dir[i][2]} and amplitude in eV\n"
157
+ )
158
+ myfile.write("\nc_H_ani 1.0\n")
159
+
160
+
161
+ def _write_dmi(cls, myfile):
162
+ if cls.has_dmi:
163
+ myfile.write("\nmagnetic_D\n")
164
+ for key, val in cls.dmi_ddict.items():
165
+ R, i, j = key
166
+ myfile.write(
167
+ f"{i+1} {j+1} {R[0]} {R[1]} {R[2]} {val} #between atoms type {i+1} and {j+1}, mediated by atom type {R}, shell {R}, amplitude in eV\n"
168
+ )
169
+ myfile.write(
170
+ "\nc_H_D -1.0 # coefficients to put in from of the sum - default is -1\n"
171
+ )
172
+
173
+
174
+ def _write_exchange_tensor(cls, myfile, symmetry=False):
175
+ myfile.write(
176
+ "\nmagnetic_r2_tensor #Exchange tensor elements, middle 9 entries: xx, xy, xz, yx, etc. (in units of eV) and direction in which it should be applied\n"
177
+ )
178
+
179
+ Jtensor = cls.get_J_tensor_dict()
180
+ shelldict = get_ind_shell(cls.distance_dict)
181
+ unitcell = cls.atoms.get_cell().array.reshape((3, 3))
182
+ uc_lengths = np.linalg.norm(unitcell, axis=1)
183
+ if Jtensor is not None:
184
+ for key, val in Jtensor.items():
185
+ i, j, R = key
186
+ # distance vector
187
+ dvec = cls.distance_dict[(R, i, j)][0]/nm/uc_lengths
188
+ dscalar = np.linalg.norm(dvec)
189
+ val = np.real(val)
190
+ ishell = shelldict[(R, i, j)]
191
+ myfile.write(
192
+ f"{i+1} {j+1} {ishell} {' '.join([str(x) for x in val.flatten()])} {dvec[0]} {dvec[1]} {dvec[2]} \n"
193
+ #f"{i+1} {j+1} {dscalar} {' '.join([str(x) for x in val.flatten()])} {dvec[0]} {dvec[1]} {dvec[2]} \n"
194
+ )
195
+ myfile.write(
196
+ "\nc_H_Exchten -1 apply 1/2 in front of the sum of the exchange tensor energy - default is -1\n"
197
+ )
198
+
199
+ def rattle_atoms_and_cell(atoms, stdev=0.001, cell_stdev=0.001):
200
+ """
201
+ Rattle both atomic positions and cell parameters.
202
+
203
+ Parameters:
204
+ -----------
205
+ atoms: ASE atoms object
206
+ The atoms to be rattled
207
+ stdev: float
208
+ Standard deviation for atomic displacement in Angstrom
209
+ cell_stdev: float
210
+ Standard deviation for cell parameter variation (fractional)
211
+
212
+ Returns:
213
+ --------
214
+ None
215
+ The atoms object is modified in-place
216
+ """
217
+ # Rattle atomic positions
218
+ positions = atoms.get_positions()
219
+ displacement = np.random.normal(0, stdev, positions.shape)
220
+ atoms.set_positions(positions + displacement)
221
+
222
+ # Rattle cell parameters
223
+ cell = atoms.get_cell()
224
+ cell_noise = np.random.normal(0, cell_stdev, cell.shape)
225
+ atoms.set_cell(cell * (1 + cell_noise), scale_atoms=True)
TB2J/kpoints.py CHANGED
@@ -70,7 +70,21 @@ def ir_kpts(
70
70
  # Irreducible k-points
71
71
  # print("Number of ir-kpoints: %d" % len(np.unique(mapping)))
72
72
  # print(grid[np.unique(mapping)] / np.array(mesh, dtype=float))
73
- return ird_kpts, weight
73
+
74
+ new_ir_kpts = []
75
+ new_weight = []
76
+ for ik, k in enumerate(ird_kpts):
77
+ # add k and -k to ird_kpts if -k is not in ird_kpts
78
+ if not any([np.allclose(-1.0 * k, kpt) for kpt in new_ir_kpts]):
79
+ new_ir_kpts.append(k)
80
+ new_ir_kpts.append(-1.0 * k)
81
+ new_weight.append(weight[ik] / 2)
82
+ new_weight.append(weight[ik] / 2)
83
+ else:
84
+ new_ir_kpts.append(k)
85
+ new_weight.append(weight[ik])
86
+ # return ird_kpts, weight
87
+ return np.array(new_ir_kpts), np.array(new_weight)
74
88
 
75
89
 
76
90
  def test_ir_kpts():
@@ -0,0 +1,74 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ scan the spheres in the space of (theta, phi), so that the points are uniformly distributed on a sphere.
4
+ The algorithm is based on fibonacci spiral sampling method.
5
+ Reference:
6
+ https://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/
7
+
8
+ But note that the convention of theta and phi are different from the one in the reference.
9
+ Here, cos(theta) = z, and phi is the azimuthal angle in the x-y plane.
10
+ In the reference, theta is the azimuthal angle in the x-y plane, and phi is the polar angle.
11
+ """
12
+
13
+ import numpy as np
14
+ from numpy import pi
15
+
16
+
17
+ def fibonacci_sphere(samples=100):
18
+ """
19
+ Fibonacci Sphere Sampling Method
20
+ Parameters:
21
+ samples (int): number of points to sample on the sphere
22
+ Returns:
23
+ theta and phi: numpy arrays with shape (samples,) containing the angles of the sampled points.
24
+ """
25
+ # Initialize the golden ratio and angles
26
+ goldenRatio = (1 + np.sqrt(5)) / 2
27
+ phi = np.arange(samples) * (2 * pi / goldenRatio)
28
+ theta = np.arccos(1 - 2 * np.arange(samples) / samples)
29
+ return theta, phi
30
+
31
+
32
+ def fibonacci_semisphere(samples=100):
33
+ """
34
+ Fibonacci Sphere Sampling Method for the upper hemisphere of a sphere.
35
+
36
+ Parameters:
37
+ samples (int): number of points to sample on the sphere
38
+
39
+ Returns:
40
+ theta and phi: numpy arrays with shape (samples,) containing the angles of the sampled points.
41
+ """
42
+ # Initialize the golden ratio and angles
43
+ goldenRatio = (1 + np.sqrt(5)) / 2
44
+ phi = np.arange(samples) * (2 * pi / goldenRatio)
45
+ theta = np.arccos(np.linspace(0, 1, samples))
46
+ return theta, phi
47
+
48
+
49
+ def test_fibonacci_sphere():
50
+ import matplotlib.pyplot as plt
51
+
52
+ # Generate points on the sphere
53
+ samples = 20000
54
+ theta, phi = fibonacci_sphere(samples)
55
+ # theta, phi = fibonacci_semisphere(samples)
56
+
57
+ # Convert spherical coordinates to Cartesian coordinates
58
+ x = np.cos(phi) * np.sin(theta)
59
+ y = np.sin(phi) * np.sin(theta)
60
+ z = np.cos(theta)
61
+
62
+ fig = plt.figure()
63
+ ax = fig.add_subplot(111, projection="3d")
64
+ ax.scatter(x, y, z, s=13.5)
65
+ # ax.plot(x, y, z)
66
+
67
+ # Set aspect to 'equal' for equal scaling in all directions
68
+ ax.set_aspect("equal")
69
+
70
+ plt.show()
71
+
72
+
73
+ if __name__ == "__main__":
74
+ test_fibonacci_sphere()