TB2J 0.9.5rc0__py3-none-any.whl → 0.9.7rc0__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/MAE.py +144 -27
- TB2J/MAEGreen.py +84 -0
- TB2J/anisotropy.py +672 -0
- TB2J/contour.py +8 -0
- TB2J/exchange.py +38 -172
- TB2J/exchangeCL2.py +11 -13
- TB2J/exchange_params.py +213 -0
- TB2J/green.py +62 -27
- TB2J/interfaces/__init__.py +12 -0
- TB2J/interfaces/abacus/__init__.py +4 -0
- TB2J/{abacus → interfaces/abacus}/abacus_api.py +3 -3
- TB2J/{abacus → interfaces/abacus}/abacus_wrapper.py +11 -8
- TB2J/{abacus → interfaces/abacus}/gen_exchange_abacus.py +5 -3
- TB2J/{abacus → interfaces/abacus}/orbital_api.py +4 -4
- TB2J/{abacus → interfaces/abacus}/stru_api.py +11 -11
- TB2J/{abacus → interfaces/abacus}/test_read_HRSR.py +0 -1
- TB2J/{abacus → interfaces/abacus}/test_read_stru.py +5 -3
- TB2J/interfaces/gpaw_interface.py +54 -0
- TB2J/interfaces/lawaf_interface.py +129 -0
- TB2J/interfaces/manager.py +23 -0
- TB2J/interfaces/siesta_interface.py +202 -0
- TB2J/interfaces/wannier90_interface.py +115 -0
- TB2J/io_exchange/io_exchange.py +21 -7
- TB2J/io_merge.py +2 -1
- TB2J/mathutils/fermi.py +11 -6
- TB2J/mathutils/lowdin.py +12 -2
- TB2J/mathutils/rotate_spin.py +222 -4
- TB2J/pauli.py +11 -3
- TB2J/symmetrize_J.py +120 -0
- TB2J/utils.py +82 -1
- {TB2J-0.9.5rc0.data → TB2J-0.9.7rc0.data}/scripts/abacus2J.py +5 -4
- {TB2J-0.9.5rc0.data → TB2J-0.9.7rc0.data}/scripts/siesta2J.py +21 -4
- TB2J-0.9.7rc0.data/scripts/wann2J.py +96 -0
- {TB2J-0.9.5rc0.dist-info → TB2J-0.9.7rc0.dist-info}/METADATA +5 -3
- {TB2J-0.9.5rc0.dist-info → TB2J-0.9.7rc0.dist-info}/RECORD +47 -46
- {TB2J-0.9.5rc0.dist-info → TB2J-0.9.7rc0.dist-info}/WHEEL +1 -1
- TB2J-0.9.7rc0.dist-info/entry_points.txt +3 -0
- TB2J/abacus/MAE.py +0 -320
- TB2J/abacus/__init__.py +0 -1
- TB2J/abacus/occupations.py +0 -278
- TB2J/cut_cell.py +0 -82
- TB2J/io_exchange/io_pickle.py +0 -0
- TB2J/manager.py +0 -445
- TB2J/mathutils.py +0 -12
- TB2J/patch.py +0 -50
- TB2J/spinham/h_matrix.py +0 -68
- TB2J/spinham/obtain_J.py +0 -79
- TB2J/supercell.py +0 -532
- TB2J-0.9.5rc0.data/scripts/wann2J.py +0 -194
- TB2J/{abacus → interfaces/abacus}/test_density_matrix.py +1 -1
- {TB2J-0.9.5rc0.data → TB2J-0.9.7rc0.data}/scripts/TB2J_downfold.py +0 -0
- {TB2J-0.9.5rc0.data → TB2J-0.9.7rc0.data}/scripts/TB2J_eigen.py +0 -0
- {TB2J-0.9.5rc0.data → TB2J-0.9.7rc0.data}/scripts/TB2J_magnon.py +0 -0
- {TB2J-0.9.5rc0.data → TB2J-0.9.7rc0.data}/scripts/TB2J_magnon_dos.py +0 -0
- {TB2J-0.9.5rc0.data → TB2J-0.9.7rc0.data}/scripts/TB2J_merge.py +0 -0
- {TB2J-0.9.5rc0.data → TB2J-0.9.7rc0.data}/scripts/TB2J_rotate.py +0 -0
- {TB2J-0.9.5rc0.data → TB2J-0.9.7rc0.data}/scripts/TB2J_rotateDM.py +0 -0
- {TB2J-0.9.5rc0.dist-info → TB2J-0.9.7rc0.dist-info}/LICENSE +0 -0
- {TB2J-0.9.5rc0.dist-info → TB2J-0.9.7rc0.dist-info}/top_level.txt +0 -0
TB2J/anisotropy.py
ADDED
@@ -0,0 +1,672 @@
|
|
1
|
+
import random
|
2
|
+
import numpy as np
|
3
|
+
import matplotlib.pyplot as plt
|
4
|
+
from scipy.interpolate import LinearNDInterpolator
|
5
|
+
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
|
+
|
11
|
+
@dataclass
|
12
|
+
class Anisotropy:
|
13
|
+
T: np.ndarray = None
|
14
|
+
direction: np.ndarray = None
|
15
|
+
amplitude: float = None
|
16
|
+
isotropic_part: float = None
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def from_T6(cls, T6):
|
20
|
+
T = T6_to_T(T6)
|
21
|
+
T -= np.trace(T) / 3 * np.eye(3)
|
22
|
+
direction, amplitude = aniostropy_tensor_to_vector(T)
|
23
|
+
return cls(
|
24
|
+
direction=direction, amplitude=amplitude, isotropic_part=isotropic_part, T=T
|
25
|
+
)
|
26
|
+
|
27
|
+
@classmethod
|
28
|
+
def from_direction_amplitude(cls, direction, amplitude, isotropic_part=0.0):
|
29
|
+
T = anisotropy_vector_to_tensor(direction, amplitude)
|
30
|
+
return cls(
|
31
|
+
T=T, direction=direction, amplitude=amplitude, isotropic_part=isotropic_part
|
32
|
+
)
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def from_tensor(cls, T):
|
36
|
+
isotropic_part = np.trace(T) / 3
|
37
|
+
T -= np.trace(T) / 3 * np.eye(3)
|
38
|
+
direction, amplitude = aniostropy_tensor_to_vector(T)
|
39
|
+
return cls(
|
40
|
+
T=T, direction=direction, amplitude=amplitude, isotropic_part=isotropic_part
|
41
|
+
)
|
42
|
+
|
43
|
+
def __post_init__(self):
|
44
|
+
if self.isotropic_part is None:
|
45
|
+
self.isotropic_part = 0
|
46
|
+
if self.T is None:
|
47
|
+
self.T = anisotropy_vector_to_tensor(
|
48
|
+
self.direction, self.amplitude
|
49
|
+
) + self.isotropic_part * np.eye(3)
|
50
|
+
elif self.direction is None or self.amplitude is None:
|
51
|
+
self.isotropic_part = np.trace(self.T) / 3
|
52
|
+
# print(f'aniostropic tensor = {self.anisotropic_part}')
|
53
|
+
self.direction, self.amplitude = aniostropy_tensor_to_vector(
|
54
|
+
self.anisotropic_part
|
55
|
+
)
|
56
|
+
self.isotropic_part = np.trace(self.T) / 3
|
57
|
+
if self.T is None or self.direction is None or self.amplitude is None:
|
58
|
+
raise ValueError(
|
59
|
+
"The input does not have enough information to create the anisotropy object."
|
60
|
+
)
|
61
|
+
|
62
|
+
def is_rank_one(self):
|
63
|
+
"""
|
64
|
+
single-axis anisotropy should be rank-one.
|
65
|
+
"""
|
66
|
+
print(f"rank = {matrix_rank(self.anisotropic_part)}")
|
67
|
+
print(f"anisotropic_part = {self.anisotropic_part}")
|
68
|
+
return matrix_rank(self.anisotropic_part) == 1
|
69
|
+
|
70
|
+
def tensor(self):
|
71
|
+
return self.T
|
72
|
+
|
73
|
+
@property
|
74
|
+
def anisotropic_part(self):
|
75
|
+
return self.T - self.isotropic_part * np.eye(3)
|
76
|
+
|
77
|
+
@property
|
78
|
+
def axis(self):
|
79
|
+
return self.direction
|
80
|
+
|
81
|
+
@property
|
82
|
+
def axis_type(self):
|
83
|
+
if self.is_easy_axis():
|
84
|
+
return "easy"
|
85
|
+
else:
|
86
|
+
return "hard"
|
87
|
+
|
88
|
+
def axis_angle(self, unit="rad"):
|
89
|
+
theta = np.arccos(self.direction[2])
|
90
|
+
phi = np.arctan2(self.direction[1], self.direction[0])
|
91
|
+
if unit.startswith("deg"):
|
92
|
+
theta = theta * 180 / np.pi
|
93
|
+
phi = phi * 180 / np.pi
|
94
|
+
return theta, phi
|
95
|
+
|
96
|
+
def amplitude(self):
|
97
|
+
return self.amplitude
|
98
|
+
|
99
|
+
def is_easy_axis(self):
|
100
|
+
return self.amplitude > 0
|
101
|
+
|
102
|
+
def is_hard_axis(self):
|
103
|
+
return self.amplitude < 0
|
104
|
+
|
105
|
+
def energy_vector_form(self, S=None, angle=None, include_isotropic=False):
|
106
|
+
if S is None:
|
107
|
+
S = sphere_to_cartesian(angle)
|
108
|
+
print(f"S shape = {S.shape}")
|
109
|
+
# return anisotropy_energy_vector_form(S, *angles, self.amplitude, self.isotropic_part)
|
110
|
+
print(f"direction shape = {self.direction.shape}")
|
111
|
+
print(f"amplitude shape = {self.amplitude.shape}")
|
112
|
+
print(f"iso shape = {self.isotropic_part.shape}")
|
113
|
+
E = -self.amplitude * (S @ self.direction) ** 2
|
114
|
+
if include_isotropic:
|
115
|
+
E = E + self.isotropic_part
|
116
|
+
print(f"E shape = {E.shape}")
|
117
|
+
return E
|
118
|
+
|
119
|
+
def energy_tensor_form(self, S=None, angle=None, include_isotropic=False):
|
120
|
+
# return anisotropy_energy_tensor_form(self.T, S)
|
121
|
+
if S is None:
|
122
|
+
S = sphere_to_cartesian(angle)
|
123
|
+
if include_isotropic:
|
124
|
+
return -S.T @ self.T @ S
|
125
|
+
else:
|
126
|
+
return -S.T @ self.anisotropic_part @ S
|
127
|
+
|
128
|
+
@classmethod
|
129
|
+
def fit_from_data(cls, thetas, phis, values, test=False):
|
130
|
+
"""
|
131
|
+
Fit the anisotropic tensor to the data
|
132
|
+
parameters:
|
133
|
+
thetas: the polar angle in degree
|
134
|
+
phis: the azimuthal angle in degree
|
135
|
+
values: the anisotropic value
|
136
|
+
Return:
|
137
|
+
the anisotropic object fitted from the data
|
138
|
+
"""
|
139
|
+
angles = np.vstack([thetas, phis])
|
140
|
+
params, cov = curve_fit(anisotropy_energy, angles, values)
|
141
|
+
fitted_values = anisotropy_energy(angles, *params)
|
142
|
+
|
143
|
+
delta = fitted_values - values
|
144
|
+
|
145
|
+
# print(f'Max value = {np.max(values)}, Min value = {np.min(values)}')
|
146
|
+
if np.abs(delta).max() > 1e-5:
|
147
|
+
print(f"Warning: The fitting is not consistent with the data.")
|
148
|
+
print(f"Max-min = {np.max(values) - np.min(values)}")
|
149
|
+
print(f"delta = {np.max(np.abs(delta))}")
|
150
|
+
T = T6_to_T(params)
|
151
|
+
obj = cls(T=T)
|
152
|
+
|
153
|
+
if test:
|
154
|
+
values2 = []
|
155
|
+
for i in range(len(thetas)):
|
156
|
+
E = obj.energy_tensor_form(
|
157
|
+
angle=[thetas[i], phis[i]], include_isotropic=True
|
158
|
+
)
|
159
|
+
values2.append(E)
|
160
|
+
delta2 = np.array(values2) - values
|
161
|
+
# print(delta2)
|
162
|
+
|
163
|
+
ax = plot_3D_scatter(angles, values - np.min(values), color="r")
|
164
|
+
plot_3D_scatter(angles, values2 - np.min(values), ax=ax, color="b")
|
165
|
+
plt.show()
|
166
|
+
|
167
|
+
return obj
|
168
|
+
|
169
|
+
@classmethod
|
170
|
+
def fit_from_data_vector_form(cls, thetas, phis, values):
|
171
|
+
"""
|
172
|
+
Fit the anisotropic tensor to the data
|
173
|
+
parameters:
|
174
|
+
thetas: the polar angle in degree
|
175
|
+
phis: the azimuthal angle in degree
|
176
|
+
values: the anisotropic value
|
177
|
+
Return:
|
178
|
+
the anisotropic object fitted from the data
|
179
|
+
"""
|
180
|
+
angles = np.vstack([thetas, phis])
|
181
|
+
params, cov = curve_fit(anisotropy_energy_vector_form, angles, values)
|
182
|
+
fitted_values = anisotropy_energy_vector_form(angles, *params)
|
183
|
+
delta = fitted_values - values
|
184
|
+
print(f"Max value = {np.max(values)}, Min value = {np.min(values)}")
|
185
|
+
print(f"Max-min = {np.max(values) - np.min(values)}")
|
186
|
+
# print(f'delta = {delta}')
|
187
|
+
theta_a, phi_a, amplitude, isotropic_part = params
|
188
|
+
direction = sphere_to_cartesian([theta_a, phi_a])
|
189
|
+
return cls.from_direction_amplitude(
|
190
|
+
direction=direction, amplitude=amplitude, isotropic_part=isotropic_part
|
191
|
+
)
|
192
|
+
|
193
|
+
@classmethod
|
194
|
+
def fit_from_data_file(cls, fname, method="tensor"):
|
195
|
+
"""
|
196
|
+
Fit the anisotropic tensor to the data
|
197
|
+
parameters:
|
198
|
+
fname: the file name of the data
|
199
|
+
Return:
|
200
|
+
anisotropy: the anisotropic object
|
201
|
+
"""
|
202
|
+
data = np.loadtxt(fname)
|
203
|
+
theta, phi, value = data[:, 0], data[:, 1], data[:, 2]
|
204
|
+
if method == "tensor":
|
205
|
+
return cls.fit_from_data(theta, phi, value)
|
206
|
+
elif method == "vector":
|
207
|
+
return cls.fit_from_data_vector_form(theta, phi, value)
|
208
|
+
else:
|
209
|
+
raise ValueError(f"Unknown method {method}")
|
210
|
+
|
211
|
+
def plot_3d(self, ax=None, figname=None, show=True, surface=True):
|
212
|
+
"""
|
213
|
+
plot the anisotropic energy in all directions in 3D
|
214
|
+
S is the spin unit vector
|
215
|
+
"""
|
216
|
+
# theta, phi = np.meshgrid(theta, phi)
|
217
|
+
# value = self.energy_tensor_form(sphere_to_cartesian([theta, phi]))
|
218
|
+
# x, y, z = sphere_to_cartesian(theta, phi, )
|
219
|
+
|
220
|
+
if surface:
|
221
|
+
thetas = np.arange(0, 181, 1)
|
222
|
+
phis = np.arange(0, 362, 1)
|
223
|
+
|
224
|
+
X, Y = np.meshgrid(thetas, phis)
|
225
|
+
Z = np.zeros(X.shape)
|
226
|
+
for i in range(len(thetas)):
|
227
|
+
for j in range(len(phis)):
|
228
|
+
# S = sphere_to_cartesian([thetas[i], phis[j]])
|
229
|
+
# E = self.energy_vector_form(angle=[thetas[i], phis[j]])
|
230
|
+
E = self.energy_tensor_form(angle=[thetas[i], phis[j]])
|
231
|
+
Z[j, i] = E
|
232
|
+
Z = Z - np.min(Z)
|
233
|
+
imax = np.argmax(Z)
|
234
|
+
X_max, Y_max, Z_max = (
|
235
|
+
X.flatten()[imax],
|
236
|
+
Y.flatten()[imax],
|
237
|
+
Z.flatten()[imax],
|
238
|
+
)
|
239
|
+
imin = np.argmin(Z)
|
240
|
+
X_min, Y_min, Z_min = (
|
241
|
+
X.flatten()[imin],
|
242
|
+
Y.flatten()[imin],
|
243
|
+
Z.flatten()[imin],
|
244
|
+
)
|
245
|
+
|
246
|
+
X, Y, Z = sphere_to_cartesian([X, Y], Z)
|
247
|
+
X_max, Y_max, Z_max = sphere_to_cartesian([X_max, Y_max], r=Z_max)
|
248
|
+
X_min, Y_min, Z_min = sphere_to_cartesian([X_min, Y_min], r=Z_min)
|
249
|
+
fig = plt.figure()
|
250
|
+
ax = fig.add_subplot(111, projection="3d")
|
251
|
+
ax.plot_surface(X, Y, Z, cmap="viridis", alpha=0.5)
|
252
|
+
# scatter the minimal and maximal points
|
253
|
+
ax.scatter(X_max, Y_max, Z_max, color="r", marker="o")
|
254
|
+
ax.scatter(X_min, Y_min, Z_min, color="b", marker="o")
|
255
|
+
|
256
|
+
# draw the easy axis
|
257
|
+
if self.is_easy_axis():
|
258
|
+
color = "r"
|
259
|
+
else:
|
260
|
+
color = "b"
|
261
|
+
d = self.direction
|
262
|
+
d = d / np.sign(d[0])
|
263
|
+
d *= np.abs(self.amplitude) * 2.5
|
264
|
+
ax.quiver(
|
265
|
+
-d[0],
|
266
|
+
-d[1],
|
267
|
+
-d[2],
|
268
|
+
2 * d[0],
|
269
|
+
2 * d[1],
|
270
|
+
2 * d[2],
|
271
|
+
color=color,
|
272
|
+
arrow_length_ratio=0.03,
|
273
|
+
)
|
274
|
+
|
275
|
+
else:
|
276
|
+
fig = plt.figure()
|
277
|
+
ax = fig.add_subplot(111, projection="3d")
|
278
|
+
thetas = np.arange(0, 181, 5)
|
279
|
+
phis = np.arange(0, 360, 5)
|
280
|
+
Es = []
|
281
|
+
angles = []
|
282
|
+
for t in thetas:
|
283
|
+
for p in phis:
|
284
|
+
# S = sphere_to_cartesian([t, p])
|
285
|
+
angles.append([t, p])
|
286
|
+
Es.append(
|
287
|
+
self.energy_tensor_form(angle=[t, p], include_isotropic=False)
|
288
|
+
)
|
289
|
+
# x, y, z = sphere_to_cartesian([t, p], r=E)
|
290
|
+
# ax.scatter(x,y, z, cmap='viridis')
|
291
|
+
angles = np.array(angles)
|
292
|
+
# plot_3D_scatter(angles.T, Es-np.min(Es), ax=None)
|
293
|
+
plot_3D_scatter(angles.T, Es, ax=None)
|
294
|
+
if figname is not None:
|
295
|
+
plt.savefig(figname)
|
296
|
+
plt.close()
|
297
|
+
if show:
|
298
|
+
plt.show()
|
299
|
+
|
300
|
+
|
301
|
+
def plot_3D_scatter(angles, values, ax=None, **kwargs):
|
302
|
+
if ax is None:
|
303
|
+
fig = plt.figure()
|
304
|
+
ax = fig.add_subplot(111, projection="3d")
|
305
|
+
thetas, phis = angles
|
306
|
+
for i in range(len(thetas)):
|
307
|
+
E = values[i]
|
308
|
+
t, p = thetas[i], phis[i]
|
309
|
+
x, y, z = sphere_to_cartesian([t, p], r=E)
|
310
|
+
ax.scatter(x, y, z, **kwargs)
|
311
|
+
return ax
|
312
|
+
|
313
|
+
|
314
|
+
def plot_3D_surface(fname, figname=None, show=True):
|
315
|
+
data = np.loadtxt(fname)
|
316
|
+
theta, phi, value = data[:, 0], data[:, 1], data[:, 2]
|
317
|
+
value = value - np.min(value)
|
318
|
+
|
319
|
+
imax = np.argmax(value)
|
320
|
+
angle_max = theta[imax], phi[imax]
|
321
|
+
imin = np.argmin(value)
|
322
|
+
angle_min = theta[imin], phi[imin]
|
323
|
+
amplitude = np.max(value) - np.min(value)
|
324
|
+
|
325
|
+
fig = plt.figure()
|
326
|
+
ax = fig.add_subplot(111, projection="3d")
|
327
|
+
x, y, z = sphere_to_cartesian([theta, phi], r=value)
|
328
|
+
# ax.plot_surface(x, y, z, triangles=None, cmap='viridis')
|
329
|
+
ax.scatter(x, y, z, cmap="viridis")
|
330
|
+
# ax.scatter(theta,phi, value, cmap='viridis')
|
331
|
+
# draw a surface plot with a color map.
|
332
|
+
if figname is not None:
|
333
|
+
plt.savefig(figname)
|
334
|
+
if show:
|
335
|
+
plt.show()
|
336
|
+
plt.close()
|
337
|
+
return angle_max, angle_min, amplitude
|
338
|
+
|
339
|
+
|
340
|
+
def plot_3D_surface_interpolation(fname, figname=None, show=True):
|
341
|
+
data = np.loadtxt(fname)
|
342
|
+
theta, phi, value = data[:, 0], data[:, 1], data[:, 2]
|
343
|
+
|
344
|
+
imax = np.argmax(value)
|
345
|
+
angle_max = theta[imax], phi[imax]
|
346
|
+
imin = np.argmin(value)
|
347
|
+
angle_min = theta[imin], phi[imin]
|
348
|
+
amplitude = np.max(value) - np.min(value)
|
349
|
+
|
350
|
+
value = value - np.min(value)
|
351
|
+
# interploate the data
|
352
|
+
interp = LinearNDInterpolator((theta, phi), value)
|
353
|
+
thetas = np.arange(0, 181, 1)
|
354
|
+
phis = np.arange(0, 340, 1)
|
355
|
+
|
356
|
+
X, Y = np.meshgrid(thetas, phis)
|
357
|
+
Z = interp(X, Y)
|
358
|
+
print(Z)
|
359
|
+
# print(np.max(Z), np.min(Z))
|
360
|
+
Z = Z - np.min(Z)
|
361
|
+
X, Y, Z = sphere_to_cartesian([X, Y], Z)
|
362
|
+
fig = plt.figure()
|
363
|
+
ax = fig.add_subplot(111, projection="3d")
|
364
|
+
ax.plot_surface(X, Y, Z, cmap="viridis")
|
365
|
+
if figname is not None:
|
366
|
+
plt.savefig(figname)
|
367
|
+
if show:
|
368
|
+
plt.show()
|
369
|
+
plt.close()
|
370
|
+
return angle_max, angle_min, amplitude
|
371
|
+
|
372
|
+
|
373
|
+
def sphere_to_cartesian(angles, r=1):
|
374
|
+
"""
|
375
|
+
Transform the spherical coordinates to the cartesian coordinates
|
376
|
+
parameters:
|
377
|
+
angles: the polar and azimuthal angle in degree
|
378
|
+
r: the radius
|
379
|
+
Return:
|
380
|
+
x, y, z: the cartesian coordinates
|
381
|
+
"""
|
382
|
+
# print(angles)
|
383
|
+
theta, phi = angles
|
384
|
+
theta = theta * np.pi / 180
|
385
|
+
phi = phi * np.pi / 180
|
386
|
+
x = r * np.sin(theta) * np.cos(phi)
|
387
|
+
y = r * np.sin(theta) * np.sin(phi)
|
388
|
+
z = r * np.cos(theta)
|
389
|
+
return np.array([x, y, z])
|
390
|
+
|
391
|
+
|
392
|
+
def cartesian_to_sphere(xyz):
|
393
|
+
"""
|
394
|
+
Transform the cartesian coordinates to the spherical coordinates
|
395
|
+
parameters:
|
396
|
+
xyz: the cartesian coordinates
|
397
|
+
Return:
|
398
|
+
theta, phi: the polar and azimuthal angle in degree
|
399
|
+
"""
|
400
|
+
x, y, z = xyz
|
401
|
+
r = np.linalg.norm(xyz)
|
402
|
+
theta = np.arccos(z / r)
|
403
|
+
phi = np.arctan2(y, x)
|
404
|
+
theta = theta * 180 / np.pi
|
405
|
+
phi = phi * 180 / np.pi
|
406
|
+
return np.array([theta, phi])
|
407
|
+
|
408
|
+
|
409
|
+
def anisotropy_energy(angle, Txx, Tyy, Tzz, Tyz, Tzx, Txy):
|
410
|
+
"""
|
411
|
+
Calculate the anisotropic energy
|
412
|
+
parameters:
|
413
|
+
angle: the polar and azimuthal angle in degree
|
414
|
+
Txx, Tyy, Tzz, Tyz, Tzx, Txy: the anisotropic tensor
|
415
|
+
Return:
|
416
|
+
E: the anisotropic energy
|
417
|
+
"""
|
418
|
+
# transform the tensor to the matrix form
|
419
|
+
T = np.array([[Txx, Txy, Tzx], [Txy, Tyy, Tyz], [Tzx, Tyz, Tzz]])
|
420
|
+
|
421
|
+
S = sphere_to_cartesian(angle)
|
422
|
+
E = -np.diag(S.T @ T @ S)
|
423
|
+
return E
|
424
|
+
|
425
|
+
|
426
|
+
def anisotropy_energy_vector_form(S, k_theta, k_phi, amplitude, isotropic_part=0):
|
427
|
+
"""
|
428
|
+
Calculate the anisotropic energy from the vector form
|
429
|
+
parameters:
|
430
|
+
S: the spin vector
|
431
|
+
kx, ky, kz: the direction of the anisotropy
|
432
|
+
"""
|
433
|
+
Scart = sphere_to_cartesian(S)
|
434
|
+
k = sphere_to_cartesian([k_theta, k_phi])
|
435
|
+
print(f"k shape = {k.shape}")
|
436
|
+
print(f"Scart shape = {Scart.shape}")
|
437
|
+
E = [-amplitude * (Si @ k) ** 2 + isotropic_part for Si in Scart.T]
|
438
|
+
return E
|
439
|
+
|
440
|
+
|
441
|
+
def T6_to_T(T6):
|
442
|
+
"""
|
443
|
+
Transform the anisotropic tensor to the matrix form
|
444
|
+
"""
|
445
|
+
Txx, Tyy, Tzz, Tyz, Tzx, Txy = T6
|
446
|
+
T = np.array([[Txx, Txy, Tzx], [Txy, Tyy, Tyz], [Tzx, Tyz, Tzz]])
|
447
|
+
return T
|
448
|
+
|
449
|
+
|
450
|
+
def is_rank_one(T):
|
451
|
+
"""
|
452
|
+
Check if the tensor is rank one
|
453
|
+
"""
|
454
|
+
return matrix_rank(T) == 1
|
455
|
+
|
456
|
+
|
457
|
+
def fit_anisotropy(thetas, phis, values):
|
458
|
+
"""
|
459
|
+
Fit the anisotropic tensor to the data
|
460
|
+
parameters:
|
461
|
+
theta: the polar angle in degree
|
462
|
+
phi: the azimuthal angle in degree
|
463
|
+
value: the anisotropic value
|
464
|
+
Return:
|
465
|
+
T: the anisotropic tensor
|
466
|
+
"""
|
467
|
+
# transform the data to the cartesian coordinates
|
468
|
+
# fit the data to the anisotropic energy
|
469
|
+
angles = np.vstack([thetas, phis])
|
470
|
+
params, cov = curve_fit(anisotropy_energy, angles, values)
|
471
|
+
# check if the fitting is consistent with the data
|
472
|
+
T = T6_to_T(params)
|
473
|
+
direction, amp = anisostropy_tensor_to_vector(T)
|
474
|
+
return direction, amp
|
475
|
+
|
476
|
+
|
477
|
+
def anisotropy_energy_vector_form2(direction, amplitude, S):
|
478
|
+
"""
|
479
|
+
Calculate the anisotropic energy
|
480
|
+
parameters:
|
481
|
+
direction: the easy axis/ hard axis of the anisotropy
|
482
|
+
amplitude: the amplitude of the anisotropy
|
483
|
+
S: the spin vector
|
484
|
+
Return:
|
485
|
+
E: the anisotropic energy, E = - amplitude * (S @ direction)**2
|
486
|
+
"""
|
487
|
+
# normalize the direction
|
488
|
+
direction = direction / np.linalg.norm(direction)
|
489
|
+
print(f"direction shape = {direction.shape}")
|
490
|
+
E = -amplitude * (S @ direction) ** 2
|
491
|
+
return E
|
492
|
+
|
493
|
+
|
494
|
+
def anisotropy_energy_tensor_form(T, S):
|
495
|
+
"""
|
496
|
+
Calculate the anisotropic energy
|
497
|
+
parameters:
|
498
|
+
T: the anisotropic tensor
|
499
|
+
S: the spin vector
|
500
|
+
Return:
|
501
|
+
E: the anisotropic energy, E = - S.T @ T @ S
|
502
|
+
"""
|
503
|
+
E = -S.T @ T @ S
|
504
|
+
return E
|
505
|
+
|
506
|
+
|
507
|
+
def anisotropy_vector_to_tensor(direction, amplitude):
|
508
|
+
"""
|
509
|
+
Transform the anisotropic vector to the anisotropic tensor
|
510
|
+
parameters:
|
511
|
+
direction: the easy axis/ hard axis of the anisotropy (direction is normalized)
|
512
|
+
amplitude: the amplitude of the anisotropy
|
513
|
+
Return:
|
514
|
+
T: the anisotropic tensor
|
515
|
+
"""
|
516
|
+
direction = direction / np.linalg.norm(direction)
|
517
|
+
T = amplitude * np.outer(direction, direction)
|
518
|
+
return T
|
519
|
+
|
520
|
+
|
521
|
+
def aniostropy_tensor_to_vector(T):
|
522
|
+
"""
|
523
|
+
Transform the anisotropic tensor to the anisotropic vector
|
524
|
+
parameters:
|
525
|
+
T: the anisotropic tensor
|
526
|
+
Return:
|
527
|
+
direction: the easy axis/ hard axis of the anisotropy
|
528
|
+
amplitude: the amplitude of the anisotropy, if the anisotropy is positive, the easy axis is the easy axis, otherwise, the hard axis is the easy axis
|
529
|
+
"""
|
530
|
+
w, v = np.linalg.eig(T)
|
531
|
+
if not is_rank_one(T):
|
532
|
+
# print("Warning: The anisotropy tensor is not rank one. The tensor cannot be transformed to the vector form.")
|
533
|
+
# print(f"The eigenvalues are {w}.")
|
534
|
+
pass
|
535
|
+
index = np.argmax(np.abs(w))
|
536
|
+
direction = v[:, index]
|
537
|
+
direction = direction / np.sign(direction[0])
|
538
|
+
amplitude = w[index]
|
539
|
+
return direction, amplitude
|
540
|
+
|
541
|
+
|
542
|
+
def test_anisotorpy_vector_to_tensor():
|
543
|
+
direction = np.random.rand(3)
|
544
|
+
direction = direction / np.linalg.norm(direction)
|
545
|
+
amplitude = random.uniform(-1, 1)
|
546
|
+
S = np.random.rand(3)
|
547
|
+
S = S / np.linalg.norm(S)
|
548
|
+
T = anisotropy_vector_to_tensor(direction, amplitude)
|
549
|
+
E_vector = anisotropy_energy_vector_form(direction, amplitude, S)
|
550
|
+
E_tensor = anisotropy_energy_tensor_form(T, S)
|
551
|
+
diff = E_vector - E_tensor
|
552
|
+
print(f"diff = {diff}")
|
553
|
+
assert np.abs(diff) < 1e-10
|
554
|
+
|
555
|
+
# test if the inverse transformation get the direction and amplitude back
|
556
|
+
dir2, amp2 = aniostropy_tensor_to_vector(T)
|
557
|
+
|
558
|
+
# set the first element of the direction to be positive
|
559
|
+
if direction[0] * dir2[0] < 0:
|
560
|
+
dir2 = -dir2
|
561
|
+
diff = np.linalg.norm(dir2 - direction)
|
562
|
+
# print(f'direction = {direction}, amplitude = {amplitude}')
|
563
|
+
# print(f'dir2 = {dir2}, amp2 = {amp2}')
|
564
|
+
assert diff < 1e-10
|
565
|
+
assert np.abs(amp2 - amplitude) < 1e-10
|
566
|
+
|
567
|
+
|
568
|
+
def test_anisotropy_tensor_to_vector():
|
569
|
+
T = np.random.rand(3, 3)
|
570
|
+
T = T + T.T
|
571
|
+
T = T - np.trace(T) / 3
|
572
|
+
direction, amplitude = aniostropy_tensor_to_vector(T)
|
573
|
+
T2 = anisotropy_vector_to_tensor(direction, amplitude)
|
574
|
+
print(f"T = {T}")
|
575
|
+
print(f"T2 = {T2}")
|
576
|
+
diff = np.linalg.norm(T - T2)
|
577
|
+
print(f"diff = {diff}")
|
578
|
+
assert diff < 1e-10
|
579
|
+
|
580
|
+
|
581
|
+
def test_fit_anisotropy():
|
582
|
+
data = np.loadtxt("anisotropy.dat")
|
583
|
+
theta, phi, value = data[:, 0], data[:, 1], data[:, 2]
|
584
|
+
angles = np.vstack([theta, phi])
|
585
|
+
T6 = fit_anisotropy(theta, phi, value)
|
586
|
+
T = T6_to_T(T6)
|
587
|
+
if not is_rank_one(T):
|
588
|
+
print("Warning: The anisotropy tensor is not rank one. ")
|
589
|
+
fitted_values = anisotropy_energy(angles, *T6)
|
590
|
+
delta = fitted_values - value
|
591
|
+
print(f"Max value = {np.max(value)}, Min value = {np.min(value)}")
|
592
|
+
print(
|
593
|
+
f"Max fitted value = {np.max(fitted_values)}, Min fitted value = {np.min(fitted_values)}"
|
594
|
+
)
|
595
|
+
for i in range(len(theta)):
|
596
|
+
print(
|
597
|
+
f"theta = {theta[i]}, phi = {phi[i]}, value = {value[i]}, fitted value = {fitted_values[i]}, delta = {delta[i]}"
|
598
|
+
)
|
599
|
+
|
600
|
+
easy_axis = easy_axis_from_tensor(T6)
|
601
|
+
print(f"easy_axis = {easy_axis}")
|
602
|
+
|
603
|
+
|
604
|
+
def view_anisotropy_strain():
|
605
|
+
strains1 = [(x, 0.0) for x in np.arange(0.000, 0.021, 0.002)]
|
606
|
+
strains2 = [(0.02, y) for y in np.arange(0.000, 0.021, 0.002)]
|
607
|
+
strains = strains1 + strains2
|
608
|
+
path = Path("anisotropy_strain_from_tensor")
|
609
|
+
path.mkdir(exist_ok=True)
|
610
|
+
fname = "a.dat"
|
611
|
+
fh = open(fname, "w")
|
612
|
+
fh.write("# s0 s1 axis amplitude axis_type angle_111 \n")
|
613
|
+
for strain in strains:
|
614
|
+
s0, s1 = strain
|
615
|
+
ani = Anisotropy.fit_from_data_file(f"a{s0:.3f}_b{s1:.3f}_plusU.dat")
|
616
|
+
dx, dy, dz = ani.direction
|
617
|
+
angle_from_111 = (
|
618
|
+
np.arccos(ani.direction @ np.array([1, 1, 1]) / np.sqrt(3)) * 180 / np.pi
|
619
|
+
)
|
620
|
+
fh.write(
|
621
|
+
f"{s0:.3f} {s1:.3f} ( {dx:.3f} {dy:.3f} {dz:.3f} ) {ani.amplitude:.5f} {ani.axis_type} {angle_from_111} \n"
|
622
|
+
)
|
623
|
+
ani.plot_3d(
|
624
|
+
surface=True, figname=path / f"a{s0:.3f}_b{s1:.3f}_plusU.png", show=False
|
625
|
+
)
|
626
|
+
fh.close()
|
627
|
+
|
628
|
+
|
629
|
+
def view_anisotropy_strain_raw():
|
630
|
+
strains1 = [(x, 0.0) for x in np.arange(0.000, 0.021, 0.002)]
|
631
|
+
strains2 = [(0.02, y) for y in np.arange(0.000, 0.021, 0.002)]
|
632
|
+
strains = strains1 + strains2
|
633
|
+
path = Path("anisotropy_strain_raw")
|
634
|
+
path.mkdir(exist_ok=True)
|
635
|
+
fname = "a_raw.dat"
|
636
|
+
fh = open(fname, "w")
|
637
|
+
fh.write("# s0 s1 direction(max) direction(min) amplitude\n")
|
638
|
+
for strain in strains:
|
639
|
+
print(f"strain = {strain}")
|
640
|
+
s0, s1 = strain
|
641
|
+
# plot_3D_surface(f"a{s0:.3f}_b{s1:.3f}_plusU.dat")
|
642
|
+
angle_max, angle_min, amplitude = plot_3D_surface(
|
643
|
+
f"a{s0:.3f}_b{s1:.3f}_plusU.dat",
|
644
|
+
figname=path / f"a{s0:.3f}_b{s1:.3f}_plusU.png",
|
645
|
+
)
|
646
|
+
dmax = sphere_to_cartesian(angle_max)
|
647
|
+
dmin = sphere_to_cartesian(angle_min)
|
648
|
+
fh.write(
|
649
|
+
f"{s0:.3f} {s1:.3f} ( {dmax[0]:.3f} {dmax[1]:.3f} {dmax[2]:.3f} ) ({dmin[0]:.3f} {dmin[1]:.3f} {dmin[2]:.3f}) {amplitude:.5f} \n"
|
650
|
+
)
|
651
|
+
|
652
|
+
plt.close()
|
653
|
+
|
654
|
+
|
655
|
+
if __name__ == "__main__":
|
656
|
+
# test_fit_anisotropy()
|
657
|
+
# test_anisotorpy_vector_to_tensor()
|
658
|
+
# test_anisotropy_tensor_to_vector()
|
659
|
+
# ani = Anisotropy.fit_from_data("anisotropy.dat")
|
660
|
+
# ani = Anisotropy.fit_from_data_file("a0.002_b0.000_plusU.dat")
|
661
|
+
# ani = Anisotropy.fit_from_data_file("a0.002_b0.000_plusU.dat", method='tensor')
|
662
|
+
# print(f'direction = {ani.direction}')
|
663
|
+
# ani.plot_3d()
|
664
|
+
# plot_3D_surface("a0.002_b0.000_plusU.dat")
|
665
|
+
# plot_3D_surface_interpolation("a0.020_b0.020_plusU.dat")
|
666
|
+
# plot_3D_surface("a0.020_b0.020_plusU.dat")
|
667
|
+
# plot_3D_surface("a0.020_b0.000_plusU.dat")
|
668
|
+
view_anisotropy_strain()
|
669
|
+
# view_anisotropy_strain_raw()
|
670
|
+
# s0=0.000
|
671
|
+
# s1=0.000
|
672
|
+
# plot_3D_surface_interpolation(f"a{s0:.3f}_b{s1:.3f}_plusU.dat", figname=None)
|
TB2J/contour.py
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
import numpy as np
|
2
2
|
from scipy.special import roots_legendre
|
3
|
+
from TB2J.utils import simpson_nonuniform_weight
|
3
4
|
|
4
5
|
|
5
6
|
class Contour:
|
6
7
|
def __init__(self, emin, emax=0.0):
|
7
8
|
self.emin = emin
|
8
9
|
self.emax = emax
|
10
|
+
self._weights = None
|
11
|
+
|
12
|
+
@property
|
13
|
+
def weights(self):
|
14
|
+
if self._weights is None:
|
15
|
+
self._weights = simpson_nonuniform_weight(self.path)
|
16
|
+
return self._weights
|
9
17
|
|
10
18
|
def build_path_semicircle(self, npoints, endpoint=True):
|
11
19
|
R = (self.emax - self.emin) / 2.0
|