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/Jdownfolder.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
import os
|
2
2
|
from collections import defaultdict
|
3
|
+
|
3
4
|
import numpy as np
|
4
5
|
from ase.dft.kpoints import monkhorst_pack
|
6
|
+
|
5
7
|
from TB2J.io_exchange import SpinIO
|
6
8
|
from TB2J.Jtensor import decompose_J_tensor
|
7
9
|
|
@@ -73,8 +75,8 @@ class JDownfolder:
|
|
73
75
|
class PWFDownfolder:
|
74
76
|
def __init__(self, JR, Rlist, iM, iL, qmesh, atoms=None, iso_only=False, **kwargs):
|
75
77
|
from lawaf.interfaces.magnon.magnon_downfolder import (
|
76
|
-
MagnonWrapper,
|
77
78
|
MagnonDownfolder,
|
79
|
+
MagnonWrapper,
|
78
80
|
)
|
79
81
|
|
80
82
|
model = MagnonWrapper(JR, Rlist, atoms)
|
@@ -92,13 +94,13 @@ class PWFDownfolder:
|
|
92
94
|
# anchors={(0, 0, 0): (-1, -2, -3, -4)},
|
93
95
|
# anchors={(0, 0, 0): ()},
|
94
96
|
# use_proj=True,
|
95
|
-
enhance_Amn=
|
97
|
+
enhance_Amn=0.0,
|
96
98
|
)
|
97
99
|
params.update(kwargs)
|
98
100
|
wann.set_parameters(**params)
|
99
101
|
print("begin downfold")
|
100
102
|
ewf = wann.downfold()
|
101
|
-
ewf.save_hr_pickle("downfolded_JR.pickle")
|
103
|
+
# ewf.save_hr_pickle("downfolded_JR.pickle")
|
102
104
|
|
103
105
|
# Plot the band structure.
|
104
106
|
wann.plot_band_fitting(
|
@@ -135,7 +137,7 @@ class JDownfolder_pickle:
|
|
135
137
|
qmesh=[7, 7, 7],
|
136
138
|
iso_only=False,
|
137
139
|
method="pwf",
|
138
|
-
**kwargs
|
140
|
+
**kwargs,
|
139
141
|
):
|
140
142
|
self.exc = SpinIO.load_pickle(path=inpath, fname="TB2J.pickle")
|
141
143
|
|
@@ -193,7 +195,7 @@ class JDownfolder_pickle:
|
|
193
195
|
qmesh=self.qmesh,
|
194
196
|
atoms=self.atoms,
|
195
197
|
iso_only=self.iso_only,
|
196
|
-
**kwargs
|
198
|
+
**kwargs,
|
197
199
|
)
|
198
200
|
Jd, Rlist = d.get_JR()
|
199
201
|
return Jd, Rlist
|
@@ -274,10 +276,10 @@ class JDownfolder_pickle:
|
|
274
276
|
def test():
|
275
277
|
# pass
|
276
278
|
# inpath = "/home/hexu/projects/NiCl2/vasp_inputs/TB2J_results"
|
277
|
-
|
278
|
-
inpath = "/home/hexu/projects/TB2J_projects/NiCl2/TB2J_NiCl/TB2J_results"
|
279
|
-
|
280
|
-
|
279
|
+
inpath = "/home/hexu/projects/TB2J_example/CrI3/TB2J_results"
|
280
|
+
# inpath = "/home/hexu/projects/TB2J_projects/NiCl2/TB2J_NiCl/TB2J_results"
|
281
|
+
_fname = os.path.join(inpath, "TB2J.pickle")
|
282
|
+
_p = JDownfolder_pickle(
|
281
283
|
inpath=inpath, metals=["Ni"], ligands=["Cl"], outpath="TB2J_results_downfolded"
|
282
284
|
)
|
283
285
|
|
TB2J/MAEGreen.py
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
+
import gc
|
1
2
|
from pathlib import Path
|
2
3
|
|
4
|
+
import matplotlib.pyplot as plt
|
3
5
|
import numpy as np
|
4
6
|
import tqdm
|
5
7
|
from HamiltonIO.abacus.abacus_wrapper import AbacusSplitSOCParser
|
6
8
|
from HamiltonIO.model.occupations import GaussOccupations
|
7
9
|
from typing_extensions import DefaultDict
|
8
10
|
|
11
|
+
from TB2J.anisotropy import Anisotropy
|
9
12
|
from TB2J.exchange import ExchangeNCL
|
13
|
+
from TB2J.external import p_map
|
14
|
+
from TB2J.mathutils.fibonacci_sphere import fibonacci_semisphere
|
10
15
|
|
11
16
|
# from HamiltonIO.model.rotate_spin import rotate_Matrix_from_z_to_axis, rotate_Matrix_from_z_to_sperical
|
12
17
|
# from TB2J.abacus.abacus_wrapper import AbacusWrapper, AbacusParser
|
@@ -22,12 +27,21 @@ def get_occupation(evals, kweights, nel, width=0.1):
|
|
22
27
|
|
23
28
|
class MAEGreen(ExchangeNCL):
|
24
29
|
def __init__(self, angles=None, **kwargs):
|
30
|
+
"""
|
31
|
+
angles are defined as theta, phi pairs, where theta is the angle between the z-axis and the magnetization direction, and phi is the angle between the x-axis and the projection of the magnetization direction on the x-y plane.
|
32
|
+
"""
|
25
33
|
super().__init__(**kwargs)
|
26
34
|
self.natoms = len(self.atoms)
|
27
35
|
if angles is None or angles == "axis":
|
28
36
|
self.set_angles_axis()
|
29
37
|
elif angles == "scan":
|
30
38
|
self.set_angles_scan()
|
39
|
+
elif angles == "fib":
|
40
|
+
self.set_angles_fib()
|
41
|
+
elif angles == "random":
|
42
|
+
self.set_angles_random()
|
43
|
+
elif angles == "miller":
|
44
|
+
self.set_angles_miller()
|
31
45
|
else:
|
32
46
|
self.thetas = angles[0]
|
33
47
|
self.phis = angles[1]
|
@@ -38,9 +52,32 @@ class MAEGreen(ExchangeNCL):
|
|
38
52
|
self.es_atom_orb = DefaultDict(lambda: 0)
|
39
53
|
|
40
54
|
def set_angles_axis(self):
|
41
|
-
"""theta and phi are defined as the x, y, z, axis."""
|
42
|
-
self.thetas = [0, np.pi / 2, np.pi / 2, np.pi / 2, np.pi]
|
43
|
-
self.phis = [0, 0, np.pi / 2, np.pi / 4, 0]
|
55
|
+
"""theta and phi are defined as the x, y, z, xy, yz, xz, xyz, x-yz, -xyz, -x-yz axis."""
|
56
|
+
self.thetas = [0, np.pi / 2, np.pi / 2, np.pi / 2, np.pi, 0, np.pi / 2, 0, 0, 0]
|
57
|
+
self.phis = [0, 0, np.pi / 2, np.pi / 4, 0, 0, 0, np.pi]
|
58
|
+
|
59
|
+
def set_angles_miller(self, nmax=2):
|
60
|
+
"""theta and angles corresponding to the miller index. remove duplicates.
|
61
|
+
e.g. 002 and 001 are the same.
|
62
|
+
"""
|
63
|
+
thetas = []
|
64
|
+
phis = []
|
65
|
+
for k in range(0, nmax + 1):
|
66
|
+
for j in range(-nmax, nmax + 1):
|
67
|
+
for i in range(-nmax, nmax + 1):
|
68
|
+
if i == 0 and j == 0 and k == 0:
|
69
|
+
continue
|
70
|
+
thetas.append(np.arccos(k / np.sqrt(i**2 + j**2 + k**2)))
|
71
|
+
if i == 0 and j == 0:
|
72
|
+
phis.append(0)
|
73
|
+
else:
|
74
|
+
phis.append(np.arctan2(j, i))
|
75
|
+
self.thetas = thetas
|
76
|
+
self.phis = phis
|
77
|
+
self.angle_pairs = list(zip(thetas, phis))
|
78
|
+
# remove duplicates of angles using sets.
|
79
|
+
self.angle_pairs = list(set(self.angle_pairs))
|
80
|
+
self.thetas, self.phis = zip(*self.angle_pairs)
|
44
81
|
|
45
82
|
def set_angles_scan(self, step=15):
|
46
83
|
self.thetas = []
|
@@ -50,6 +87,21 @@ class MAEGreen(ExchangeNCL):
|
|
50
87
|
self.thetas.append(i * np.pi / 180)
|
51
88
|
self.phis.append(j * np.pi / 180)
|
52
89
|
|
90
|
+
def set_angles_random(self, n=16):
|
91
|
+
# n random pairs of theta, phi
|
92
|
+
self.thetas = np.random.random(n) * np.pi
|
93
|
+
self.phis = np.random.random(n) * 2 * np.pi
|
94
|
+
|
95
|
+
def set_angles_fib(self, n=35):
|
96
|
+
self.thetas, self.phis = fibonacci_semisphere(n)
|
97
|
+
# thetas and phis are np.array
|
98
|
+
# add (theta, phi): (pi/2, pi/2) and (pi/2, pi/4)
|
99
|
+
# self.thetas += [np.pi / 2, np.pi / 2]
|
100
|
+
# self.phis += [np.pi / 2, np.pi / 4]
|
101
|
+
for i in range(8):
|
102
|
+
self.thetas = np.concatenate([self.thetas, [np.pi / 2]])
|
103
|
+
self.phis = np.concatenate([self.phis, [np.pi * i / 8]])
|
104
|
+
|
53
105
|
def get_band_energy_vs_angles_from_eigen(
|
54
106
|
self,
|
55
107
|
thetas,
|
@@ -78,6 +130,13 @@ class MAEGreen(ExchangeNCL):
|
|
78
130
|
|
79
131
|
def get_perturbed(self, e, thetas, phis):
|
80
132
|
self.tbmodel.set_so_strength(0.0)
|
133
|
+
# maxsoc = self.tbmodel.get_max_Hsoc_abs()
|
134
|
+
# maxH0 = self.tbmodel.get_max_H0_spin_abs()
|
135
|
+
# if maxsoc > maxH0 * 0.01:
|
136
|
+
# print(f"""Warning: The SOC of the Hamiltonian has a maximum of {maxsoc} eV,
|
137
|
+
# comparing to the maximum of {maxH0} eV of the spin part of the Hamiltonian.
|
138
|
+
# The SOC is too strong, the perturbation theory may not be valid.""")
|
139
|
+
|
81
140
|
G0K = self.G.get_Gk_all(e)
|
82
141
|
Hsoc_k = self.tbmodel.get_Hk_soc(self.G.kpts)
|
83
142
|
na = len(thetas)
|
@@ -92,12 +151,12 @@ class MAEGreen(ExchangeNCL):
|
|
92
151
|
GdH = G0K[ik] @ dHi
|
93
152
|
# dE += np.trace(GdH @ G0K[i].T.conj() @ dHi) * self.kweights[i]
|
94
153
|
# diagonal of second order perturbation.
|
95
|
-
dG2diag = np.diag(GdH @ GdH)
|
154
|
+
# dG2diag = np.diag(GdH @ GdH)
|
96
155
|
# dG2 = np.einsum("ij,ji->ij", GdH, GdH)
|
97
156
|
dG2 = GdH * GdH.T
|
98
157
|
dG2sum = np.sum(dG2)
|
99
158
|
# print(f"dG2sum-sum: {dG2sum}")
|
100
|
-
dG2sum = np.sum(dG2diag)
|
159
|
+
# dG2sum = np.sum(dG2diag)
|
101
160
|
|
102
161
|
# dG2sum = np.trace(GdH @ GdH)
|
103
162
|
# print(f"dG2sum-Tr: {dG2sum}")
|
@@ -118,13 +177,11 @@ class MAEGreen(ExchangeNCL):
|
|
118
177
|
+ dE_atom_orb[1::2, ::2]
|
119
178
|
+ dE_atom_orb[::2, 1::2]
|
120
179
|
)
|
180
|
+
dE_atom = np.sum(dE_atom_orb)
|
121
181
|
mmat = self.mmats[iatom]
|
122
182
|
dE_atom_orb = mmat.T @ dE_atom_orb @ mmat
|
123
|
-
|
124
183
|
dE_angle_atom_orb[(iangle, iatom)] += dE_atom_orb
|
125
|
-
|
126
|
-
dE_atom = np.sum(dG2diag[iorb]) * self.G.kweights[ik]
|
127
|
-
# dE_atom = np.sum(dE_atom_orb)
|
184
|
+
# dE_atom = np.sum(dG2diag[iorb]) * self.G.kweights[ik]
|
128
185
|
dE_angle_atom[iangle, iatom] += dE_atom
|
129
186
|
return dE_angle, dE_angle_atom, dE_angle_atom_orb
|
130
187
|
|
@@ -143,15 +200,33 @@ class MAEGreen(ExchangeNCL):
|
|
143
200
|
if with_eigen:
|
144
201
|
self.es2 = self.get_band_energy_vs_angles_from_eigen(thetas, phis)
|
145
202
|
|
146
|
-
for ie, e in enumerate(tqdm.tqdm(self.contour.path)):
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
203
|
+
# for ie, e in enumerate(tqdm.tqdm(self.contour.path)):
|
204
|
+
# dE_angle, dE_angle_atom, dE_angle_atom_orb = self.get_perturbed(
|
205
|
+
# e, thetas, phis
|
206
|
+
# )
|
207
|
+
# self.es += dE_angle * self.contour.weights[ie]
|
208
|
+
# self.es_atom += dE_angle_atom * self.contour.weights[ie]
|
209
|
+
# for key, value in dE_angle_atom_orb.items():
|
210
|
+
# self.es_atom_orb[key] += (
|
211
|
+
# dE_angle_atom_orb[key] * self.contour.weights[ie]
|
212
|
+
# )
|
213
|
+
|
214
|
+
# rewrite the for loop above to use p_map
|
215
|
+
def func(e):
|
216
|
+
return self.get_perturbed(e, thetas, phis)
|
217
|
+
|
218
|
+
if self.nproc > 1:
|
219
|
+
results = p_map(func, self.contour.path, num_cpus=self.nproc)
|
220
|
+
else:
|
221
|
+
npole = len(self.contour.path)
|
222
|
+
results = map(func, tqdm.tqdm(self.contour.path, total=npole))
|
223
|
+
for i, result in enumerate(results):
|
224
|
+
dE_angle, dE_angle_atom, dE_angle_atom_orb = result
|
225
|
+
self.es += dE_angle * self.contour.weights[i]
|
226
|
+
self.es_atom += dE_angle_atom * self.contour.weights[i]
|
152
227
|
for key, value in dE_angle_atom_orb.items():
|
153
228
|
self.es_atom_orb[key] += (
|
154
|
-
dE_angle_atom_orb[key] * self.contour.weights[
|
229
|
+
dE_angle_atom_orb[key] * self.contour.weights[i]
|
155
230
|
)
|
156
231
|
|
157
232
|
self.es = -np.imag(self.es) / (2 * np.pi)
|
@@ -159,10 +234,25 @@ class MAEGreen(ExchangeNCL):
|
|
159
234
|
for key, value in self.es_atom_orb.items():
|
160
235
|
self.es_atom_orb[key] = -np.imag(value) / (2 * np.pi)
|
161
236
|
|
162
|
-
def
|
237
|
+
def fit_anisotropy_tensor(self):
|
238
|
+
self.ani = Anisotropy.fit_from_data(self.thetas, self.phis, self.es)
|
239
|
+
return self.ani
|
240
|
+
|
241
|
+
def output(
|
242
|
+
self,
|
243
|
+
output_path="TB2J_anisotropy",
|
244
|
+
with_eigen=True,
|
245
|
+
figure3d="MAE_3d.png",
|
246
|
+
figure_contourf="MAE_contourf.png",
|
247
|
+
):
|
163
248
|
Path(output_path).mkdir(exist_ok=True)
|
164
249
|
fname = f"{output_path}/MAE.dat"
|
165
250
|
fname_orb = f"{output_path}/MAE_orb.dat"
|
251
|
+
fname_tensor = f"{output_path}/MAE_tensor.dat"
|
252
|
+
if figure3d is not None:
|
253
|
+
fname_fig3d = f"{output_path}/{figure3d}"
|
254
|
+
if figure_contourf is not None:
|
255
|
+
fname_figcontourf = f"{output_path}/{figure_contourf}"
|
166
256
|
|
167
257
|
# ouput with eigenvalues.
|
168
258
|
if with_eigen:
|
@@ -178,15 +268,29 @@ class MAEGreen(ExchangeNCL):
|
|
178
268
|
f.write("\n")
|
179
269
|
|
180
270
|
with open(fname, "w") as f:
|
181
|
-
f.write("# theta, phi, MAE(total), MAE(atom-wise) Unit: meV\n")
|
271
|
+
f.write("# theta (rad), phi(rad), MAE(total), MAE(atom-wise) Unit: meV\n")
|
182
272
|
for i, (theta, phi, e, es) in enumerate(
|
183
273
|
zip(self.thetas, self.phis, self.es, self.es_atom)
|
184
274
|
):
|
185
|
-
f.write(f"{theta:.5f} {phi:.5f} {e*1e3:.8f} ")
|
275
|
+
f.write(f"{theta%np.pi:.5f} {phi%(2*np.pi):.5f} {e*1e3:.8f} ")
|
186
276
|
for ea in es:
|
187
277
|
f.write(f"{ea*1e3:.8f} ")
|
188
278
|
f.write("\n")
|
189
279
|
|
280
|
+
self.ani = self.fit_anisotropy_tensor()
|
281
|
+
with open(fname_tensor, "w") as f:
|
282
|
+
f.write("# Anisotropy tensor in meV\n")
|
283
|
+
f.write(f"{self.ani.tensor_strings(include_isotropic=False)}\n")
|
284
|
+
|
285
|
+
if figure3d is not None:
|
286
|
+
self.ani.plot_3d(figname=fname_fig3d, show=False)
|
287
|
+
|
288
|
+
if figure_contourf is not None:
|
289
|
+
self.ani.plot_contourf(figname=fname_figcontourf, show=False)
|
290
|
+
|
291
|
+
plt.close()
|
292
|
+
gc.collect()
|
293
|
+
|
190
294
|
with open(fname_orb, "w") as f:
|
191
295
|
f.write("=" * 80 + "\n")
|
192
296
|
f.write("Orbitals for each atom: \n")
|
TB2J/anisotropy.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
import random
|
2
|
-
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from pathlib import Path
|
4
|
+
|
3
5
|
import matplotlib.pyplot as plt
|
6
|
+
import numpy as np
|
7
|
+
from numpy.linalg import matrix_rank
|
4
8
|
from scipy.interpolate import LinearNDInterpolator
|
5
9
|
from scipy.optimize import curve_fit
|
6
|
-
from numpy.linalg import matrix_rank
|
7
|
-
from dataclasses import dataclass
|
8
|
-
from pathlib import Path
|
9
10
|
|
10
11
|
|
11
12
|
@dataclass
|
@@ -18,8 +19,9 @@ class Anisotropy:
|
|
18
19
|
@classmethod
|
19
20
|
def from_T6(cls, T6):
|
20
21
|
T = T6_to_T(T6)
|
21
|
-
|
22
|
-
|
22
|
+
isotropic_part = np.trace(T) / 3
|
23
|
+
T -= isotropic_part * np.eye(3)
|
24
|
+
direction, amplitude = anisotropy_tensor_to_vector(T)
|
23
25
|
return cls(
|
24
26
|
direction=direction, amplitude=amplitude, isotropic_part=isotropic_part, T=T
|
25
27
|
)
|
@@ -33,9 +35,9 @@ class Anisotropy:
|
|
33
35
|
|
34
36
|
@classmethod
|
35
37
|
def from_tensor(cls, T):
|
36
|
-
isotropic_part = np.trace(T) / 3
|
38
|
+
isotropic_part = np.trace(T) / 3 * np.eye(3)
|
37
39
|
T -= np.trace(T) / 3 * np.eye(3)
|
38
|
-
direction, amplitude =
|
40
|
+
direction, amplitude = anisotropy_tensor_to_vector(T)
|
39
41
|
return cls(
|
40
42
|
T=T, direction=direction, amplitude=amplitude, isotropic_part=isotropic_part
|
41
43
|
)
|
@@ -44,13 +46,14 @@ class Anisotropy:
|
|
44
46
|
if self.isotropic_part is None:
|
45
47
|
self.isotropic_part = 0
|
46
48
|
if self.T is None:
|
47
|
-
self.T =
|
48
|
-
self.direction, self.amplitude
|
49
|
-
|
49
|
+
self.T = (
|
50
|
+
anisotropy_vector_to_tensor(self.direction, self.amplitude)
|
51
|
+
+ self.isotropic_part
|
52
|
+
)
|
50
53
|
elif self.direction is None or self.amplitude is None:
|
51
|
-
self.isotropic_part = np.trace(self.T) / 3
|
52
|
-
# print(f'
|
53
|
-
self.direction, self.amplitude =
|
54
|
+
self.isotropic_part = np.trace(self.T) / 3 * np.eye(3)
|
55
|
+
# print(f'anisotropic tensor = {self.anisotropic_part}')
|
56
|
+
self.direction, self.amplitude = anisotropy_tensor_to_vector(
|
54
57
|
self.anisotropic_part
|
55
58
|
)
|
56
59
|
self.isotropic_part = np.trace(self.T) / 3
|
@@ -113,7 +116,6 @@ class Anisotropy:
|
|
113
116
|
E = -self.amplitude * (S @ self.direction) ** 2
|
114
117
|
if include_isotropic:
|
115
118
|
E = E + self.isotropic_part
|
116
|
-
print(f"E shape = {E.shape}")
|
117
119
|
return E
|
118
120
|
|
119
121
|
def energy_tensor_form(self, S=None, angle=None, include_isotropic=False):
|
@@ -126,7 +128,7 @@ class Anisotropy:
|
|
126
128
|
return -S.T @ self.anisotropic_part @ S
|
127
129
|
|
128
130
|
@classmethod
|
129
|
-
def fit_from_data(cls, thetas, phis, values, test=False):
|
131
|
+
def fit_from_data(cls, thetas, phis, values, test=False, units="rad"):
|
130
132
|
"""
|
131
133
|
Fit the anisotropic tensor to the data
|
132
134
|
parameters:
|
@@ -137,14 +139,16 @@ class Anisotropy:
|
|
137
139
|
the anisotropic object fitted from the data
|
138
140
|
"""
|
139
141
|
angles = np.vstack([thetas, phis])
|
142
|
+
if units.lower().startswith("deg"):
|
143
|
+
angles = np.deg2rad(angles)
|
140
144
|
params, cov = curve_fit(anisotropy_energy, angles, values)
|
141
145
|
fitted_values = anisotropy_energy(angles, *params)
|
142
146
|
|
143
147
|
delta = fitted_values - values
|
144
148
|
|
145
149
|
# print(f'Max value = {np.max(values)}, Min value = {np.min(values)}')
|
146
|
-
if np.abs(delta).max() > 1e-
|
147
|
-
print(
|
150
|
+
if np.abs(delta).max() > 1e-4:
|
151
|
+
print("Warning: The fitting is not consistent with the data.")
|
148
152
|
print(f"Max-min = {np.max(values) - np.min(values)}")
|
149
153
|
print(f"delta = {np.max(np.abs(delta))}")
|
150
154
|
T = T6_to_T(params)
|
@@ -157,7 +161,7 @@ class Anisotropy:
|
|
157
161
|
angle=[thetas[i], phis[i]], include_isotropic=True
|
158
162
|
)
|
159
163
|
values2.append(E)
|
160
|
-
delta2 = np.array(values2) - values
|
164
|
+
# delta2 = np.array(values2) - values
|
161
165
|
# print(delta2)
|
162
166
|
|
163
167
|
ax = plot_3D_scatter(angles, values - np.min(values), color="r")
|
@@ -183,7 +187,7 @@ class Anisotropy:
|
|
183
187
|
delta = fitted_values - values
|
184
188
|
print(f"Max value = {np.max(values)}, Min value = {np.min(values)}")
|
185
189
|
print(f"Max-min = {np.max(values) - np.min(values)}")
|
186
|
-
|
190
|
+
print(f"delta = {delta}")
|
187
191
|
theta_a, phi_a, amplitude, isotropic_part = params
|
188
192
|
direction = sphere_to_cartesian([theta_a, phi_a])
|
189
193
|
return cls.from_direction_amplitude(
|
@@ -208,6 +212,25 @@ class Anisotropy:
|
|
208
212
|
else:
|
209
213
|
raise ValueError(f"Unknown method {method}")
|
210
214
|
|
215
|
+
@classmethod
|
216
|
+
def fit_from_xyz_data_file(cls, fname, method="tensor"):
|
217
|
+
"""
|
218
|
+
Fit the anisotropic tensor to the data with x y z val form
|
219
|
+
parameters:
|
220
|
+
fname: the file name of the data
|
221
|
+
Return:
|
222
|
+
anisotropy: the anisotropic object
|
223
|
+
"""
|
224
|
+
data = np.loadtxt(fname)
|
225
|
+
xyz, value = data[:, 0:3], data[:, 3]
|
226
|
+
theta, phi = np.array([cartesian_to_sphere(t) for t in xyz]).T
|
227
|
+
if method == "tensor":
|
228
|
+
return cls.fit_from_data(theta, phi, value)
|
229
|
+
elif method == "vector":
|
230
|
+
return cls.fit_from_data_vector_form(theta, phi, value)
|
231
|
+
else:
|
232
|
+
raise ValueError(f"Unknown method {method}")
|
233
|
+
|
211
234
|
def plot_3d(self, ax=None, figname=None, show=True, surface=True):
|
212
235
|
"""
|
213
236
|
plot the anisotropic energy in all directions in 3D
|
@@ -297,6 +320,55 @@ class Anisotropy:
|
|
297
320
|
if show:
|
298
321
|
plt.show()
|
299
322
|
|
323
|
+
def plot_contourf(self, ax=None, figname=None, show=False):
|
324
|
+
if ax is None:
|
325
|
+
fig, ax = plt.subplots()
|
326
|
+
X, Y = np.meshgrid(np.arange(0, 180, 1), np.arange(-180, 180, 1))
|
327
|
+
Z = np.zeros(X.shape)
|
328
|
+
ntheta, nphi = X.shape
|
329
|
+
for i in range(ntheta):
|
330
|
+
for j in range(nphi):
|
331
|
+
E = self.energy_tensor_form(angle=[X[i, j], Y[i, j]])
|
332
|
+
Z[i, j] = E
|
333
|
+
# find the X, Y for min and max of Z
|
334
|
+
X_max, Y_max = np.unravel_index(np.argmax(Z), Z.shape)
|
335
|
+
X_min, Y_min = np.unravel_index(np.argmin(Z), Z.shape)
|
336
|
+
X_max, Y_max = X[X_max, Y_max], Y[X_max, Y_max]
|
337
|
+
X_min, Y_min = X[X_min, Y_min], Y[X_min, Y_min]
|
338
|
+
c = ax.contourf(X, Y, Z, cmap="viridis", levels=200)
|
339
|
+
# print(X_max, Y_max, X_min, Y_min)
|
340
|
+
# ax.scatter(X_max, Y_max, color="r", marker="o")
|
341
|
+
# ax.scatter(X_min, Y_min, color="b", marker="o")
|
342
|
+
ax.set_xlabel("$\theta$ (degree)")
|
343
|
+
ax.set_ylabel("$\phi$ degree")
|
344
|
+
# ax.scatter(X_max, Y_max, color="r", marker="o")
|
345
|
+
# ax.scatter(X_min, Y_min, color="r", marker="o")
|
346
|
+
|
347
|
+
# colorbar
|
348
|
+
_cbar = plt.colorbar(c, ax=ax)
|
349
|
+
|
350
|
+
if figname is not None:
|
351
|
+
plt.savefig(figname)
|
352
|
+
plt.close()
|
353
|
+
if show:
|
354
|
+
print(f"Max = {X_max}, {Y_max}, Min = {X_min}, {Y_min}")
|
355
|
+
plt.show()
|
356
|
+
return ax
|
357
|
+
|
358
|
+
def tensor_strings(self, include_isotropic=False, multiplier=1):
|
359
|
+
"""
|
360
|
+
convert the energy tensor to strings for easy printing
|
361
|
+
parameters:
|
362
|
+
include_isotropic: if include the isotropic part
|
363
|
+
multiplier: the multiplier for the tensor. Use for scaling the tensor by units.
|
364
|
+
"""
|
365
|
+
if include_isotropic:
|
366
|
+
T = self.T
|
367
|
+
else:
|
368
|
+
T = self.T
|
369
|
+
strings = np.array2string(T * multiplier, precision=5, separator=" ")
|
370
|
+
return strings
|
371
|
+
|
300
372
|
|
301
373
|
def plot_3D_scatter(angles, values, ax=None, **kwargs):
|
302
374
|
if ax is None:
|
@@ -389,7 +461,7 @@ def sphere_to_cartesian(angles, r=1):
|
|
389
461
|
return np.array([x, y, z])
|
390
462
|
|
391
463
|
|
392
|
-
def cartesian_to_sphere(xyz):
|
464
|
+
def cartesian_to_sphere(xyz, unit="deg"):
|
393
465
|
"""
|
394
466
|
Transform the cartesian coordinates to the spherical coordinates
|
395
467
|
parameters:
|
@@ -401,8 +473,13 @@ def cartesian_to_sphere(xyz):
|
|
401
473
|
r = np.linalg.norm(xyz)
|
402
474
|
theta = np.arccos(z / r)
|
403
475
|
phi = np.arctan2(y, x)
|
404
|
-
|
405
|
-
|
476
|
+
if unit.lower().startswith("deg"):
|
477
|
+
theta = theta * 180 / np.pi
|
478
|
+
phi = phi * 180 / np.pi
|
479
|
+
elif unit.lower.startswith("rad"):
|
480
|
+
pass
|
481
|
+
else:
|
482
|
+
raise ValueError("unit must be 'deg' or 'rad'")
|
406
483
|
return np.array([theta, phi])
|
407
484
|
|
408
485
|
|
@@ -470,7 +547,7 @@ def fit_anisotropy(thetas, phis, values):
|
|
470
547
|
params, cov = curve_fit(anisotropy_energy, angles, values)
|
471
548
|
# check if the fitting is consistent with the data
|
472
549
|
T = T6_to_T(params)
|
473
|
-
direction, amp =
|
550
|
+
direction, amp = anisotropy_tensor_to_vector(T)
|
474
551
|
return direction, amp
|
475
552
|
|
476
553
|
|
@@ -518,7 +595,7 @@ def anisotropy_vector_to_tensor(direction, amplitude):
|
|
518
595
|
return T
|
519
596
|
|
520
597
|
|
521
|
-
def
|
598
|
+
def anisotropy_tensor_to_vector(T):
|
522
599
|
"""
|
523
600
|
Transform the anisotropic tensor to the anisotropic vector
|
524
601
|
parameters:
|
@@ -553,7 +630,7 @@ def test_anisotorpy_vector_to_tensor():
|
|
553
630
|
assert np.abs(diff) < 1e-10
|
554
631
|
|
555
632
|
# test if the inverse transformation get the direction and amplitude back
|
556
|
-
dir2, amp2 =
|
633
|
+
dir2, amp2 = anisotropy_tensor_to_vector(T)
|
557
634
|
|
558
635
|
# set the first element of the direction to be positive
|
559
636
|
if direction[0] * dir2[0] < 0:
|
@@ -569,7 +646,7 @@ def test_anisotropy_tensor_to_vector():
|
|
569
646
|
T = np.random.rand(3, 3)
|
570
647
|
T = T + T.T
|
571
648
|
T = T - np.trace(T) / 3
|
572
|
-
direction, amplitude =
|
649
|
+
direction, amplitude = anisotropy_tensor_to_vector(T)
|
573
650
|
T2 = anisotropy_vector_to_tensor(direction, amplitude)
|
574
651
|
print(f"T = {T}")
|
575
652
|
print(f"T2 = {T2}")
|
@@ -597,9 +674,6 @@ def test_fit_anisotropy():
|
|
597
674
|
f"theta = {theta[i]}, phi = {phi[i]}, value = {value[i]}, fitted value = {fitted_values[i]}, delta = {delta[i]}"
|
598
675
|
)
|
599
676
|
|
600
|
-
easy_axis = easy_axis_from_tensor(T6)
|
601
|
-
print(f"easy_axis = {easy_axis}")
|
602
|
-
|
603
677
|
|
604
678
|
def view_anisotropy_strain():
|
605
679
|
strains1 = [(x, 0.0) for x in np.arange(0.000, 0.021, 0.002)]
|
TB2J/contour.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import numpy as np
|
2
2
|
from scipy.special import roots_legendre
|
3
|
+
|
3
4
|
from TB2J.utils import simpson_nonuniform_weight
|
4
5
|
|
5
6
|
|
@@ -15,6 +16,21 @@ class Contour:
|
|
15
16
|
self._weights = simpson_nonuniform_weight(self.path)
|
16
17
|
return self._weights
|
17
18
|
|
19
|
+
def integrate_func(self, func):
|
20
|
+
"""
|
21
|
+
integrate f along the path
|
22
|
+
"""
|
23
|
+
return np.dot(func(self.path), self.weights)
|
24
|
+
|
25
|
+
def integrate_values(self, values):
|
26
|
+
"""
|
27
|
+
integrate f along the path
|
28
|
+
"""
|
29
|
+
ret = 0
|
30
|
+
for i in range(len(values)):
|
31
|
+
ret += values[i] * self.weights[i]
|
32
|
+
return ret
|
33
|
+
|
18
34
|
def build_path_semicircle(self, npoints, endpoint=True):
|
19
35
|
R = (self.emax - self.emin) / 2.0
|
20
36
|
R0 = (self.emin + self.emax) / 2.0
|