Source code for radtools.magnons.magnons

from radtools.exchange.hamiltonian import ExchangeHamiltonian
from radtools.exchange.parameter import ExchangeParameter
from radtools.routines import span_orthonormal_set
from radtools.crystal.atom import Atom
from radtools.magnons.diagonalization import solve_via_colpa, ColpaFailed
from radtools.routines import print_2d_array

from copy import deepcopy


from scipy.spatial.transform import Rotation
import numpy as np
from math import sqrt, pi

import matplotlib.pyplot as plt

__all__ = ["MagnonDispersion"]


[docs] class MagnonDispersion: r""" Magnon dispersion wrapper. Parameters ---------- model : :py:class:`.ExchangeHamiltonian` Exchange Hamiltonian. Q : (3,) |array_like|_ Ordering wave vector of the spin-spiral. In relative coordinates with respect to the model`s reciprocal cell. It rotates spins from unit cell to unit cell, but not from atom to atom in (0, 0, 0) unit cell. n : (3,) |array_like|_, optional Global rotational axis. If None provided, then it is set to the direction of ``Q``. Attributes ---------- Q : (3,) :numpy:`ndarray` Ordering wave vector of the spin-spiral. in absolute coordinates in reciprocal space. n : (3,) :numpy:`ndarray` Global rotational axis. N : int Number of magnetic atoms. J : (M,) :numpy:`ndarray` Exchange parameters. i : (M,) :numpy:`ndarray` Indices of the first atom in the exchange pair. j : (M,) :numpy:`ndarray` Indices of the second atom in the exchange pair. d : (M, 3) :numpy:`ndarray` Vectors from the first atom to the second atom in the exchange pair. S : (N, 3) :numpy:`ndarray` Spin vectors. u : (N, 3) :numpy:`ndarray` Defined from local spin directions. v : (N, 3) :numpy:`ndarray` Defined from local spin directions. """ def __init__(self, model: ExchangeHamiltonian, Q=None, n=None): # Store the exchange model, but privately self._model = deepcopy(model) self._model.notation = "SpinW" # Convert Q to absolute coordinates if Q is None: Q = [0, 0, 0] self.Q = np.array(Q, dtype=float) @ self._model.crystal.lattice.reciprocal_cell # Convert n to absolute coordinates, use Q if n is not provided if n is None: if np.allclose([0, 0, 0], Q): self.n = np.array([0, 0, 1]) else: self.n = Q / np.linalg.norm(Q) else: self.n = np.array(n, dtype=float) / np.linalg.norm(n) # Get the number of magnetic atoms self.N = len(self._model.magnetic_atoms) # Get the exchange parameters, indices and vectors form the ExchangeHamiltonian self.J, self.i, self.j, self.d = self._model.input_for_magnons() # Initialize spin vector, u vector and v vector arrays self.S = np.zeros((self.N, 3), dtype=float) self.u = np.zeros((self.N, 3), dtype=complex) self.v = np.zeros((self.N, 3), dtype=complex) # Get spin vectors and compute u and v vectors from local spin directions for a_i, atom in enumerate(self._model.magnetic_atoms): try: self.S[a_i] = atom.spin_vector e1, e2, e3 = span_orthonormal_set(self.S[a_i]) self.v[a_i] = e3 self.u[a_i] = e1 + 1j * e2 except ValueError: raise ValueError( f"Spin vector is not defined for {atom.fullname} atom." ) # Rotate exchange matrices for i in range(len(self.J)): rotvec = self.n * (Q @ self.d[i]) R_nm = Rotation.from_rotvec(rotvec).as_matrix() self.J[i] = self.J[i] @ R_nm
[docs] def omega(self, k, return_none=False): r""" Computes magnon energies. Parameters ---------- k : (3,) |array_like|_ Reciprocal vector. In absolute coordinates. return_none : bool, default=False If True, then return None if Colpa fails. Returns ------- omegas : (N,) :numpy:`ndarray` Magnon energies for the vector ``k``. """ # Initialize matrices A_1 = np.zeros((self.N, self.N), dtype=complex) A_2 = np.zeros((self.N, self.N), dtype=complex) B = np.zeros((self.N, self.N), dtype=complex) C = np.zeros((self.N, self.N), dtype=complex) # Compute A(k) (A_1), A(-k)^* (A_2), B and C matrices for index in range(len(self.J)): i = self.i[index] j = self.j[index] A_1[i][j] += ( sqrt(np.linalg.norm(self.S[i]) * np.linalg.norm(self.S[j])) / 2 * (self.u[i] @ self.J[index] @ np.conjugate(self.u[j])) * np.exp(1j * (k @ self.d[index])) ) A_2[i][j] += ( sqrt(np.linalg.norm(self.S[i]) * np.linalg.norm(self.S[j])) / 2 * (self.u[i] @ self.J[index] @ np.conjugate(self.u[j])) * np.exp(-1j * (k @ self.d[index])) ) B[i][j] += ( sqrt(np.linalg.norm(self.S[i]) * np.linalg.norm(self.S[j])) / 2 * (self.u[i] @ self.J[index] @ self.u[j]) * np.exp(1j * (k @ self.d[index])) ) C[i, i] += np.linalg.norm(self.S[j]) * ( self.v[i] @ self.J[index] @ self.v[j] ) # Compute h matrix left = np.concatenate((2 * A_1 - 2 * C, 2 * np.conjugate(B).T), axis=0) right = np.concatenate((2 * B, 2 * np.conjugate(A_2) - 2 * C), axis=0) h = np.concatenate((left, right), axis=1) # Diagonalize h matrix via Colpa try: omegas, U = solve_via_colpa(h) omegas = omegas.real[: self.N] except ColpaFailed: if return_none: omegas = np.array([None] * self.N) else: omegas = np.zeros(self.N) return omegas