TB2J 0.9.4rc0__py3-none-any.whl → 0.9.6rc0__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 (58) hide show
  1. TB2J/MAE.py +108 -24
  2. TB2J/anisotropy.py +672 -0
  3. TB2J/contour.py +8 -0
  4. TB2J/exchange.py +43 -103
  5. TB2J/exchangeCL2.py +11 -13
  6. TB2J/exchange_params.py +213 -0
  7. TB2J/green.py +62 -27
  8. TB2J/interfaces/__init__.py +12 -0
  9. TB2J/interfaces/abacus/__init__.py +4 -0
  10. TB2J/{abacus → interfaces/abacus}/abacus_api.py +3 -3
  11. TB2J/{abacus → interfaces/abacus}/abacus_wrapper.py +11 -7
  12. TB2J/{abacus → interfaces/abacus}/gen_exchange_abacus.py +6 -3
  13. TB2J/{abacus → interfaces/abacus}/orbital_api.py +4 -4
  14. TB2J/{abacus → interfaces/abacus}/stru_api.py +11 -11
  15. TB2J/{abacus → interfaces/abacus}/test_read_HRSR.py +0 -1
  16. TB2J/{abacus → interfaces/abacus}/test_read_stru.py +5 -3
  17. TB2J/interfaces/gpaw_interface.py +54 -0
  18. TB2J/interfaces/lawaf_interface.py +129 -0
  19. TB2J/interfaces/manager.py +23 -0
  20. TB2J/interfaces/siesta_interface.py +174 -0
  21. TB2J/interfaces/wannier90_interface.py +115 -0
  22. TB2J/io_exchange/io_exchange.py +21 -7
  23. TB2J/io_merge.py +2 -1
  24. TB2J/mathutils/fermi.py +11 -6
  25. TB2J/mathutils/lowdin.py +12 -2
  26. TB2J/mathutils/rotate_spin.py +222 -4
  27. TB2J/pauli.py +38 -2
  28. TB2J/symmetrize_J.py +120 -0
  29. TB2J/utils.py +82 -1
  30. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/abacus2J.py +5 -4
  31. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/siesta2J.py +21 -4
  32. TB2J-0.9.6rc0.data/scripts/wann2J.py +96 -0
  33. {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/METADATA +4 -3
  34. {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/RECORD +46 -46
  35. {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/WHEEL +1 -1
  36. TB2J-0.9.6rc0.dist-info/entry_points.txt +3 -0
  37. TB2J/abacus/MAE.py +0 -320
  38. TB2J/abacus/__init__.py +0 -1
  39. TB2J/abacus/occupations.py +0 -278
  40. TB2J/cut_cell.py +0 -82
  41. TB2J/io_exchange/io_pickle.py +0 -0
  42. TB2J/manager.py +0 -441
  43. TB2J/mathutils.py +0 -12
  44. TB2J/patch.py +0 -50
  45. TB2J/spinham/h_matrix.py +0 -68
  46. TB2J/spinham/obtain_J.py +0 -79
  47. TB2J/supercell.py +0 -532
  48. TB2J-0.9.4rc0.data/scripts/wann2J.py +0 -194
  49. TB2J/{abacus → interfaces/abacus}/test_density_matrix.py +1 -1
  50. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_downfold.py +0 -0
  51. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_eigen.py +0 -0
  52. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_magnon.py +0 -0
  53. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_magnon_dos.py +0 -0
  54. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_merge.py +0 -0
  55. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_rotate.py +0 -0
  56. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_rotateDM.py +0 -0
  57. {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/LICENSE +0 -0
  58. {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.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