|
|
|
@ -29,6 +29,25 @@ tau_z = np.array([[1, 0], [0, -1]])
|
|
|
|
|
tau_0 = np.array([[1, 0], [0, 1]])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def commutator(a, b):
|
|
|
|
|
"""Shorthand for commutator.
|
|
|
|
|
|
|
|
|
|
Commutator of two matrices in the mathematical sense.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
a : np.array_like
|
|
|
|
|
The first matrix
|
|
|
|
|
b : np.array_like
|
|
|
|
|
The second matrix
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
np.array_like
|
|
|
|
|
The commutator of a and b
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return a @ b - b @ a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# define some useful functions
|
|
|
|
|
def hsk(H, ss, sc_off, k=(0, 0, 0)):
|
|
|
|
|
"""Speed up Hk and Sk generation.
|
|
|
|
@ -36,14 +55,20 @@ def hsk(H, ss, sc_off, k=(0, 0, 0)):
|
|
|
|
|
Calculates the Hamiltonian and the Overlap matrix at a given k point. It is faster that the sisl version.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
H (np.array_like): Hamiltonian in spin box form
|
|
|
|
|
ss (np.array_like): Overlap matrix in spin box form
|
|
|
|
|
sc_off (list): supercell indexes of the Hamiltonian
|
|
|
|
|
k (tuple, optional): The k point where the matrices are set up. Defaults to (0, 0, 0).
|
|
|
|
|
H : np.array_like
|
|
|
|
|
Hamiltonian in spin box form
|
|
|
|
|
ss : np.array_like
|
|
|
|
|
Overlap matrix in spin box form
|
|
|
|
|
sc_off : list
|
|
|
|
|
supercell indexes of the Hamiltonian
|
|
|
|
|
k : tuple, optional
|
|
|
|
|
The k point where the matrices are set up. Defaults to (0, 0, 0)
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
np.array_like: Hamiltonian at the given k point
|
|
|
|
|
np.array_like: Overlap matrix at the given k point
|
|
|
|
|
np.array_like
|
|
|
|
|
Hamiltonian at the given k point
|
|
|
|
|
np.array_like
|
|
|
|
|
Overlap matrix at the given k point
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# this two conversion lines are from the sisl source
|
|
|
|
@ -60,46 +85,6 @@ def hsk(H, ss, sc_off, k=(0, 0, 0)):
|
|
|
|
|
return HK, SK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_contour(emin=-20, emax=0.0, enum=42, p=150):
|
|
|
|
|
"""A more sophisticated contour generator.
|
|
|
|
|
|
|
|
|
|
Calculates the parameters for the complex contour integral.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
emin (int, optional): Energy minimum of the contour. Defaults to -20.
|
|
|
|
|
emax (float, optional): Energy maximum of the contour. Defaults to 0.0, so the Fermi level.
|
|
|
|
|
enum (int, optional): Number of sample points along the contour. Defaults to 42.
|
|
|
|
|
p (int, optional): Shape parameter that describes the distribution of the sample points. Defaults to 150.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
ccont: ccont
|
|
|
|
|
Contains all the information for the contour integral. Should clarify later...
|
|
|
|
|
"""
|
|
|
|
|
x, wl = roots_legendre(enum)
|
|
|
|
|
R = (emax - emin) / 2
|
|
|
|
|
z0 = (emax + emin) / 2
|
|
|
|
|
y1 = -np.log(1 + np.pi * p)
|
|
|
|
|
y2 = 0
|
|
|
|
|
|
|
|
|
|
y = (y2 - y1) / 2 * x + (y2 + y1) / 2
|
|
|
|
|
phi = (np.exp(-y) - 1) / p
|
|
|
|
|
ze = z0 + R * np.exp(1j * phi)
|
|
|
|
|
we = -(y2 - y1) / 2 * np.exp(-y) / p * 1j * (ze - z0) * wl
|
|
|
|
|
|
|
|
|
|
# just an empty container class
|
|
|
|
|
class ccont:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
cont = ccont()
|
|
|
|
|
cont.R = R
|
|
|
|
|
cont.z0 = z0
|
|
|
|
|
cont.ze = ze
|
|
|
|
|
cont.we = we
|
|
|
|
|
cont.enum = enum
|
|
|
|
|
|
|
|
|
|
return cont
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_kset(dirs="xyz", NUMK=20):
|
|
|
|
|
"""Simple k-grid generator to sample the Brillouin zone.
|
|
|
|
|
|
|
|
|
@ -110,12 +95,17 @@ def make_kset(dirs="xyz", NUMK=20):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
dirs (str, optional): Directions of the k points in the Brillouin zone. They are the three lattice vectors. Defaults to "xyz".
|
|
|
|
|
NUMK (int, optional): The number of k points in a direction. Defaults to 20.
|
|
|
|
|
dirs : str, optional
|
|
|
|
|
Directions of the k points in the Brillouin zone. They are the three lattice vectors. Defaults to "xyz"
|
|
|
|
|
NUMK : int, optional
|
|
|
|
|
The number of k points in a direction. Defaults to 20
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
np.array_like: An array of k points that uniformly sample the Brillouin zone in the given directions.
|
|
|
|
|
np.array_like
|
|
|
|
|
An array of k points that uniformly sample the Brillouin zone in the given directions
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# if there is no xyz in dirs return the Gamma point
|
|
|
|
|
if not (sum([d in dirs for d in "xyz"])):
|
|
|
|
|
return np.array([[0, 0, 0]])
|
|
|
|
|
|
|
|
|
@ -133,19 +123,50 @@ def make_kset(dirs="xyz", NUMK=20):
|
|
|
|
|
return kset
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def commutator(a, b):
|
|
|
|
|
"""Shorthand for commutator.
|
|
|
|
|
def make_contour(emin=-20, emax=0.0, enum=42, p=150):
|
|
|
|
|
"""A more sophisticated contour generator.
|
|
|
|
|
|
|
|
|
|
Commutator of two matrices in the mathematical sense.
|
|
|
|
|
Calculates the parameters for the complex contour integral. It uses the
|
|
|
|
|
Legendre-Gauss quadrature method. It returns a class that contains
|
|
|
|
|
the information for the contour integral.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
a (np.array_like): The first matrix.
|
|
|
|
|
b (np.array_like): The second matrix
|
|
|
|
|
emin : int, optional
|
|
|
|
|
Energy minimum of the contour. Defaults to -20
|
|
|
|
|
emax : float, optional
|
|
|
|
|
Energy maximum of the contour. Defaults to 0.0, so the Fermi level
|
|
|
|
|
enum : int, optional
|
|
|
|
|
Number of sample points along the contour. Defaults to 42
|
|
|
|
|
p : int, optional
|
|
|
|
|
Shape parameter that describes the distribution of the sample points. Defaults to 150
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
np.array_like: The commutator of a and b.
|
|
|
|
|
ccont
|
|
|
|
|
Contains all the information for the contour integral
|
|
|
|
|
"""
|
|
|
|
|
return a @ b - b @ a
|
|
|
|
|
x, wl = roots_legendre(enum)
|
|
|
|
|
R = (emax - emin) / 2 # radius
|
|
|
|
|
z0 = (emax + emin) / 2 # center point
|
|
|
|
|
y1 = -np.log(1 + np.pi * p) # lower bound
|
|
|
|
|
y2 = 0 # upper bound
|
|
|
|
|
|
|
|
|
|
y = (y2 - y1) / 2 * x + (y2 + y1) / 2
|
|
|
|
|
phi = (np.exp(-y) - 1) / p # angle parameter
|
|
|
|
|
ze = z0 + R * np.exp(1j * phi) # complex points for path
|
|
|
|
|
we = -(y2 - y1) / 2 * np.exp(-y) / p * 1j * (ze - z0) * wl
|
|
|
|
|
|
|
|
|
|
# just an empty container class
|
|
|
|
|
class ccont:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
cont = ccont()
|
|
|
|
|
cont.R = R
|
|
|
|
|
cont.z0 = z0
|
|
|
|
|
cont.ze = ze
|
|
|
|
|
cont.we = we
|
|
|
|
|
cont.enum = enum
|
|
|
|
|
|
|
|
|
|
return cont
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def tau_u(u):
|
|
|
|
@ -154,10 +175,12 @@ def tau_u(u):
|
|
|
|
|
Returns the vector u in the basis of the Pauli matrices.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
u (list or np.array_like): The direction
|
|
|
|
|
u : list or np.array_like
|
|
|
|
|
The direction
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
np.array_like: Arbitrary direction in the base of the Pauli matrices.
|
|
|
|
|
np.array_like
|
|
|
|
|
Arbitrary direction in the base of the Pauli matrices
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# u is force to be of unit length
|
|
|
|
@ -173,10 +196,12 @@ def crossM(u):
|
|
|
|
|
It acts as a cross product with vector u.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
u (list or np.array_like): The second vector in the cross product
|
|
|
|
|
u : list or np.array_like
|
|
|
|
|
The second vector in the cross product
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
np.array_like: The matrix that represents teh cross product with a vector.
|
|
|
|
|
np.array_like
|
|
|
|
|
The matrix that represents teh cross product with a vector
|
|
|
|
|
"""
|
|
|
|
|
return np.array([[0, -u[2], u[1]], [u[2], 0, -u[0]], [-u[1], u[0], 0]])
|
|
|
|
|
|
|
|
|
@ -186,12 +211,16 @@ def RotM(theta, u, eps=1e-10):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
theta (float): The angle of rotation.
|
|
|
|
|
u (np.array_like): The rotation axis.
|
|
|
|
|
eps (float, optional): Cutoff for small elements in the resulting matrix. Defaults to 1e-10.
|
|
|
|
|
theta : float
|
|
|
|
|
The angle of rotation
|
|
|
|
|
u : np.array_like
|
|
|
|
|
The rotation axis
|
|
|
|
|
eps : float, optional
|
|
|
|
|
Cutoff for small elements in the resulting matrix. Defaults to 1e-10
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
np.array_like: The rotation matrix.
|
|
|
|
|
np.array_like
|
|
|
|
|
The rotation matrix
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
u = u / np.linalg.norm(u)
|
|
|
|
@ -213,12 +242,16 @@ def RotMa2b(a, b, eps=1e-10):
|
|
|
|
|
Function returns array R such that R @ a = b holds.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
a (np.array_like): First vector.
|
|
|
|
|
b (np.array_like): Second vector.
|
|
|
|
|
eps (float, optional): Cutoff for small elements in the resulting matrix. Defaults to 1e-10.
|
|
|
|
|
a : np.array_like
|
|
|
|
|
First vector
|
|
|
|
|
b : np.array_like
|
|
|
|
|
Second vector
|
|
|
|
|
eps : float, optional
|
|
|
|
|
Cutoff for small elements in the resulting matrix. Defaults to 1e-10
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
np.array_like: The rotation matrix with the above property.
|
|
|
|
|
np.array_like
|
|
|
|
|
The rotation matrix with the above property
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
v = np.cross(a, b)
|
|
|
|
@ -230,89 +263,42 @@ def RotMa2b(a, b, eps=1e-10):
|
|
|
|
|
return M
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def spin_tracer(M):
|
|
|
|
|
"""Spin tracer utility.
|
|
|
|
|
|
|
|
|
|
This takes an operator with the orbital-spin sequence:
|
|
|
|
|
orbital 1 up,
|
|
|
|
|
orbital 1 down,
|
|
|
|
|
orbital 2 up,
|
|
|
|
|
orbital 2 down,
|
|
|
|
|
that is in the SPIN-BOX representation,
|
|
|
|
|
and extracts orbital dependent Pauli traces.
|
|
|
|
|
def read_siesta_emin(eigfile):
|
|
|
|
|
"""It reads the lowest energy level from the siesta run.
|
|
|
|
|
|
|
|
|
|
It uses the .EIG file from siesta that contains the eigenvalues.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
M (np.array_like): Traceble matrix.
|
|
|
|
|
eigfile : str
|
|
|
|
|
The path to the .EIG file
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
dict: It contains the traced matrix with "x", "y", "z" and "c".
|
|
|
|
|
float
|
|
|
|
|
The energy minimum
|
|
|
|
|
"""
|
|
|
|
|
M11 = M[0::2, 0::2]
|
|
|
|
|
M12 = M[0::2, 1::2]
|
|
|
|
|
M21 = M[1::2, 0::2]
|
|
|
|
|
M22 = M[1::2, 1::2]
|
|
|
|
|
|
|
|
|
|
M_o = dict()
|
|
|
|
|
M_o["x"] = M12 + M21
|
|
|
|
|
M_o["y"] = 1j * (M12 - M21)
|
|
|
|
|
M_o["z"] = M11 - M22
|
|
|
|
|
M_o["c"] = M11 + M22
|
|
|
|
|
|
|
|
|
|
return M_o
|
|
|
|
|
|
|
|
|
|
# read the file
|
|
|
|
|
eigs = eigSileSiesta(eigfile).read_data()
|
|
|
|
|
|
|
|
|
|
def parse_magnetic_entity(dh, atom=None, l=None, **kwargs):
|
|
|
|
|
"""Function to define orbital indexes of a given magnetic entity.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
dh (sisl.physics.Hamiltonian): Hamiltonian
|
|
|
|
|
atom (integer or list of integers, optional): Defining atom (or atoms) in the unit cell forming the magnetic entity. Defaults to None.
|
|
|
|
|
l (integer, optional): Defining the angular momentum channel. Defaults to None.
|
|
|
|
|
return eigs.min()
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
list: The orbital indexes of the given magnetic entity.
|
|
|
|
|
"""
|
|
|
|
|
# case where we deal with more than one atom defining the magnetic entity
|
|
|
|
|
if type(atom) == list:
|
|
|
|
|
dat = []
|
|
|
|
|
for a in atom:
|
|
|
|
|
a_orb_idx = dh.geometry.a2o(a, all=True)
|
|
|
|
|
if (
|
|
|
|
|
type(l) == int
|
|
|
|
|
): # if specified we restrict to given l angular momentum channel inside each atom
|
|
|
|
|
a_orb_idx = a_orb_idx[[o.l == l for o in dh.geometry.atoms[a].orbitals]]
|
|
|
|
|
dat.append(a_orb_idx)
|
|
|
|
|
orbital_indeces = np.hstack(dat)
|
|
|
|
|
# case where we deal with a singel atom magnetic entity
|
|
|
|
|
elif type(atom) == int:
|
|
|
|
|
orbital_indeces = dh.geometry.a2o(atom, all=True)
|
|
|
|
|
if (
|
|
|
|
|
type(l) == int
|
|
|
|
|
): # if specified we restrict to given l angular momentum channel
|
|
|
|
|
orbital_indeces = orbital_indeces[
|
|
|
|
|
[o.l == l for o in dh.geometry.atoms[atom].orbitals]
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
return orbital_indeces # numpy array containing integers labeling orbitals associated to a magnetic entity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def blow_up_orbindx(orb_indices):
|
|
|
|
|
"""
|
|
|
|
|
Function to blow up orbital indeces to make SPIN BOX indices.
|
|
|
|
|
"""
|
|
|
|
|
return np.array([[2 * o, 2 * o + 1] for o in orb_indices]).flatten()
|
|
|
|
|
|
|
|
|
|
def int_de_ke(traced, we):
|
|
|
|
|
"""It numerically integrates the traced matrix.
|
|
|
|
|
|
|
|
|
|
def read_siesta_emin(eigfile):
|
|
|
|
|
"""_summary_
|
|
|
|
|
It is a wrapper from numpy.trapz and it contains the
|
|
|
|
|
relevant constants to calculate the energy integral from
|
|
|
|
|
equation 93 or 96.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
eigfile (_type_): _description_
|
|
|
|
|
traced : np.array_like
|
|
|
|
|
The trace of a matrix or a matrix product
|
|
|
|
|
we : float
|
|
|
|
|
The weight of a point on the contour
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
_type_: _description_
|
|
|
|
|
float
|
|
|
|
|
The energy calculated from the integral formula
|
|
|
|
|
"""
|
|
|
|
|
eigs = eigSileSiesta(eigfile).read_data()
|
|
|
|
|
|
|
|
|
|
return eigs.min()
|
|
|
|
|
return np.trapz(-1 / np.pi * np.imag(traced * we))
|
|
|
|
|