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.
- TB2J/Jdownfolder.py +11 -9
- TB2J/MAEGreen.py +123 -19
- TB2J/anisotropy.py +104 -30
- TB2J/contour.py +16 -0
- TB2J/exchange.py +14 -17
- TB2J/exchangeCL2.py +20 -11
- TB2J/exchange_params.py +8 -0
- TB2J/green.py +15 -1
- TB2J/interfaces/siesta_interface.py +2 -1
- TB2J/io_exchange/io_exchange.py +44 -5
- TB2J/io_exchange/io_matjes.py +225 -0
- TB2J/kpoints.py +15 -1
- TB2J/mathutils/fibonacci_sphere.py +74 -0
- TB2J/mycfr.py +114 -0
- TB2J/orbital_magmom.py +36 -0
- TB2J/orbmap.py +4 -4
- TB2J/symmetrize_J.py +24 -11
- {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info}/METADATA +16 -7
- {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info}/RECORD +33 -29
- {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info}/WHEEL +1 -1
- {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_downfold.py +0 -0
- {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_eigen.py +0 -0
- {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_magnon.py +0 -0
- {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_magnon_dos.py +0 -0
- {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_merge.py +0 -0
- {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_rotate.py +0 -0
- {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/TB2J_rotateDM.py +0 -0
- {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/abacus2J.py +0 -0
- {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/siesta2J.py +0 -0
- {TB2J-0.9.9rc5.data → tb2j-0.9.9rc7.data}/scripts/wann2J.py +0 -0
- {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info}/entry_points.txt +0 -0
- {TB2J-0.9.9rc5.dist-info → tb2j-0.9.9rc7.dist-info/licenses}/LICENSE +0 -0
- {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 -
|
49
|
-
# self.emin = -
|
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="
|
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)] =
|
572
|
+
self.A_ijR[(R, iatom, jatom)] = self.contour.integrate_values(f)
|
573
|
+
|
575
574
|
if self.orb_decomposition:
|
576
|
-
self.
|
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
|
-
|
226
|
-
elif method == "simpson":
|
227
|
-
|
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
|
-
|
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)] =
|
234
|
-
self.
|
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,
|
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
|
)
|
TB2J/io_exchange/io_exchange.py
CHANGED
@@ -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
|
-
|
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
|
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(
|
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=
|
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
|
-
|
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()
|