def d2V(self, p, max_process_p=20): """Potential Hessian Compute electrostatic potential Hessian at a point or list of points, total and split by electronic and ionic contributions. Arguments: p {np.ndarray} -- List of points to compute potential Hessian at. Keyword Arguments: max_process_p {number} -- Max number of points processed at once. Lower to trade off speed for memory (default: {20}) Returns: np.ndarray -- Total potential Hessian np.ndarray -- Electronic potential Hessian np.ndarray -- Ionic potential Hessian """ # Return potential Hessian at a point or a list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] d2Ve = np.zeros((N, 3, 3)) d2Vi = np.zeros((N, 3, 3)) slices = make_process_slices(N, max_process_p) g2_mat = self._g_grid[:, None, :, :, :] * self._g_grid[None, :, :, :, :] for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) d2ftk = -g2_mat[:, :, :, :, :, None] * ftk[None, None, :, :, :, :] # Compute the electronic potential d2Ve[s] = np.real( np.sum( self._Ve_G[None, None, :, :, :, None] * d2ftk, axis=(2, 3, 4), )).T # Now add the ionic one d2Vi[s] = np.real( np.sum( self._Vi_G[None, None, :, :, :, None] * d2ftk, axis=(2, 3, 4), )).T d2Ve *= _cK * cnst.e * 1e30 # Moving to SI units d2Vi *= _cK * cnst.e * 1e30 d2V = d2Ve + d2Vi return d2V, d2Ve, d2Vi
def Hfine(self, p, contact=False, max_process_p=20): # Return hyperfine tensors at a point or a list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] HT = np.zeros((N, 3, 3)) slices = make_process_slices(N, max_process_p) for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) # Compute the electronic potential HT[s] = np.real( np.sum(self._dip_G[:, :, :, :, :, None] * ftk[None, None], axis=(2, 3, 4))).T # And Fermi contact term if contact: fermi = np.real( np.sum(self._spin_G[:, :, :, None] * ftk, axis=(0, 1, 2))) fermi *= _fermiT / (self._vol * np.prod(self._spin.shape)) HT[s] += np.eye(3)[None, :, :] * fermi[:, None, None] return HT
def V(self, p, max_process_p=20): # Return potential at a point or list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] Ve = np.zeros(N) Vi = np.zeros(N) slices = make_process_slices(N, max_process_p) for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) # Compute the electronic potential Ve[s] = np.real( np.sum(self._Ve_G[:, :, :, None] * ftk, axis=(0, 1, 2))) # Now add the ionic one Vi[s] = np.real( np.sum(self._Vi_G[:, :, :, :, None] * ftk[:, :, :, None], axis=(0, 1, 2, 3))) Ve *= _cK * cnst.e * 1e10 # Moving to SI units Vi *= _cK * cnst.e * 1e10 V = Ve + Vi return V, Ve, Vi
def rho(self, p, max_process_p=20): # Return charge density at a point or list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] rhoe = np.zeros(N) rhoi = np.zeros(N) slices = make_process_slices(N, max_process_p) for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) rhoe[s] = np.real( np.sum(self._rhoe_G[:, :, :, None] * ftk, axis=(0, 1, 2))) rhoi[s] = np.real( np.sum(self._rhoi_G[:, :, :, :, None] * ftk[:, :, :, None], axis=(0, 1, 2, 3))) # Convert units to e/Ang^3 rhoe /= self._vol rhoi /= self._vol rho = rhoe + rhoi return rho, rhoe, rhoi
def Hfine(self, p, contact=False, max_process_p=20): """Hyperfine tensor Compute hyperfine tensor at a point or list of points. Only possible for electronic densities including spin polarisation. Arguments: p {np.ndarray} -- List of points to compute hyperfine tensor at. Keyword Arguments: contact {bool} -- If True, include Fermi contact term (default: {False}) max_process_p {number} -- Max number of points processed at once. Lower to trade off speed for memory (default: {20}) Returns: np.ndarray -- Total hyperfine tensor np.ndarray -- Electronic hyperfine tensor np.ndarray -- Ionic hyperfine tensor Raises: RuntimeError -- If the electronic density is not spin polarised. """ if not self.has_spin(): raise RuntimeError("Can not compute hyperfine tensor without" " spin polarised electronic density") # Return hyperfine tensors at a point or a list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] HT = np.zeros((N, 3, 3)) slices = make_process_slices(N, max_process_p) for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) # Compute the electronic potential HT[s] = np.real( np.sum( self._dip_G[:, :, :, :, :, None] * ftk[None, None], axis=(2, 3, 4), )).T # And Fermi contact term if contact: fermi = np.real( np.sum(self._spin_G[:, :, :, None] * ftk, axis=(0, 1, 2))) fermi *= _fermiT / (self._vol * np.prod(self._spin.shape)) HT[s] += np.eye(3)[None, :, :] * fermi[:, None, None] return HT
def d2Vpart(self, p, dr=None, max_process_p=20): # Return potential at a point or list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] I = len(self.positions) d2Ve = np.zeros((3, 3, I, N)) d2Vi = np.zeros((3, 3, I, N)) if dr is None: dr = np.zeros((I, 3)) else: dr = np.array(dr) if dr.shape != (I, 3): raise ValueError('Invalid ionic displacement vector') edr = np.exp(-1.0j * np.tensordot(self._g_grid, dr.T, axes=(0, 0))) slices = make_process_slices(N, max_process_p) for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) dftk = 1.0j * self._g_grid[:, :, :, :, None] * ftk[None, :, :, :, :] g2_mat = (self._g_grid[:, None, :, :, :] * self._g_grid[None, :, :, :, :]) d2ftk = -g2_mat[:, :, :, :, :, None] * ftk[None, None, :, :, :, :] # Compute the electronic potential d2Ve[:, :, :, s] = np.real( np.sum(self._Vpart_G[None, None, :, :, :, :, None] * d2ftk[:, :, :, :, :, None, :] * edr[None, None, :, :, :, :, None], axis=(2, 3, 4))) # Now add the ionic one d2Vi[:, :, :, s] = np.real( np.sum(self._Vi_G[None, None, :, :, :, :, None] * d2ftk[:, :, :, :, :, None] * edr[None, None, :, :, :, :, None], axis=(2, 3, 4))) # Swap axes for convenience d2Ve = np.moveaxis(d2Ve, 2, 0) d2Vi = np.moveaxis(d2Vi, 2, 0) d2Ve *= _cK * cnst.e * 1e30 # Moving to SI units d2Vi *= _cK * cnst.e * 1e30 d2V = d2Ve + d2Vi return d2V, d2Ve, d2Vi
def V(self, p, max_process_p=20): """Potential Compute electrostatic potential at a point or list of points, total and split by electronic and ionic contributions. Arguments: p {np.ndarray} -- List of points to compute potential at. Keyword Arguments: max_process_p {number} -- Max number of points processed at once. Lower to trade off speed for memory (default: {20}) Returns: np.ndarray -- Total potential np.ndarray -- Electronic potential np.ndarray -- Ionic potential """ # Return potential at a point or list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] Ve = np.zeros(N) Vi = np.zeros(N) slices = make_process_slices(N, max_process_p) for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) # Compute the electronic potential Ve[s] = np.real( np.sum(self._Ve_G[:, :, :, None] * ftk, axis=(0, 1, 2))) # Now add the ionic one Vi[s] = np.real( np.sum(self._Vi_G[:, :, :, None] * ftk, axis=(0, 1, 2))) Ve *= _cK * cnst.e * 1e10 # Moving to SI units Vi *= _cK * cnst.e * 1e10 V = Ve + Vi return V, Ve, Vi
def rho(self, p, max_process_p=20): """Charge density Compute charge density at a point or list of points, total and split by electronic and ionic contributions. Arguments: p {np.ndarray} -- List of points to compute charge density at. Keyword Arguments: max_process_p {number} -- Max number of points processed at once. Lower to trade off speed for memory (default: {20}) Returns: np.ndarray -- Total charge density np.ndarray -- Electronic charge density np.ndarray -- Ionic charge density """ # Return charge density at a point or list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] rhoe = np.zeros(N) rhoi = np.zeros(N) slices = make_process_slices(N, max_process_p) for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) rhoe[s] = np.real( np.sum(self._rhoe_G[:, :, :, None] * ftk, axis=(0, 1, 2))) rhoi[s] = np.real( np.sum(self._rhoi_G[:, :, :, None] * ftk, axis=(0, 1, 2))) # Convert units to e/Ang^3 rhoe /= self._vol rhoi /= self._vol rho = rhoe + rhoi return rho, rhoe, rhoi
def Vpart(self, p, dr=None, max_process_p=20): # Return potential at a point or list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] I = len(self.positions) Ve = np.zeros((I, N)) Vi = np.zeros((I, N)) if dr is None: dr = np.zeros((I, 3)) else: dr = np.array(dr) if dr.shape != (I, 3): raise ValueError('Invalid ionic displacement vector') edr = np.exp(-1.0j * np.tensordot(self._g_grid, dr.T, axes=(0, 0))) slices = make_process_slices(N, max_process_p) for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) # Compute the electronic potential Ve[:, s] = np.real( np.sum(self._Vpart_G[:, :, :, :, None] * ftk[:, :, :, None, :] * edr[:, :, :, :, None], axis=(0, 1, 2))) # Now add the ionic one Vi[:, s] = np.real( np.sum(self._Vi_G[:, :, :, :, None] * ftk[:, :, :, None, :] * edr[:, :, :, :, None], axis=(0, 1, 2))) # Coulomb constant Ve *= _cK * cnst.e * 1e10 # Moving to SI units Vi *= _cK * cnst.e * 1e10 V = Ve + Vi return V, Ve, Vi
def rhopart(self, p, dr=None, max_process_p=20): # Return partitioned charge density at a point or list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] I = len(self.positions) rhoe = np.zeros((I, N)) rhoi = np.zeros((I, N)) if dr is None: dr = np.zeros((I, 3)) else: dr = np.array(dr) if dr.shape != (I, 3): raise ValueError('Invalid ionic displacement vector') edr = np.exp(-1.0j * np.tensordot(self._g_grid, dr.T, axes=(0, 0))) slices = make_process_slices(N, max_process_p) for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) rhoe[:, s] = np.real( np.sum(self._rhopart_G[:, :, :, :, None] * ftk[:, :, :, None] * edr[:, :, :, :, None], axis=(0, 1, 2))) rhoi[:, s] = np.real( np.sum(self._rhoipart_G[:, :, :, :, None] * ftk[:, :, :, None] * edr[:, :, :, :, None], axis=(0, 1, 2))) # Convert units to e/Ang^3 rhoe /= self._vol rhoi /= self._vol rho = rhoe + rhoi return rho, rhoe, rhoi
def d2V(self, p, max_process_p=20): # Return potential Hessian at a point or a list of points p = np.array(p) if len(p.shape) == 1: p = p[None, :] # Make it into a list of points # The point list is sliced for convenience, to avoid taking too much # memory N = p.shape[0] d2Ve = np.zeros((N, 3, 3)) d2Vi = np.zeros((N, 3, 3)) slices = make_process_slices(N, max_process_p) g2_mat = (self._g_grid[:, None, :, :, :] * self._g_grid[None, :, :, :, :]) for s in slices: # Fourier transform kernel ftk = np.exp(1.0j * np.tensordot(self._g_grid, p[s].T, axes=(0, 0))) d2ftk = -g2_mat[:, :, :, :, :, None] * ftk[None, None, :, :, :, :] # Compute the electronic potential d2Ve[s] = np.real( np.sum(self._Ve_G[None, None, :, :, :, None] * d2ftk, axis=(2, 3, 4))).T # Now add the ionic one d2Vi[s] = np.real( np.sum(self._Vi_G[None, None, :, :, :, :, None] * d2ftk[:, :, :, :, :, None], axis=(2, 3, 4, 5))).T d2Ve *= _cK * cnst.e * 1e30 # Moving to SI units d2Vi *= _cK * cnst.e * 1e30 d2V = d2Ve + d2Vi return d2V, d2Ve, d2Vi