Source code for iDEA.precondition

"""Preconditioners for self-consistent calculations.

Can be used in conjunction with mixing schemes (see mix.py).
"""
from __future__ import division
from __future__ import absolute_import

import numpy as np
import scipy.special as scsp

[docs]class StubPreconditioner(object): """Performs no preconditioning """
[docs] def __init__(self, pm): """Initializes variables parameters ---------- pm: object input parameters """
[docs] def precondition(self, f, eigv, eigf): """Return preconditioned f""" return f
[docs]class KerkerPreconditioner(object): """Performs Kerker preconditioning Performs Kerker preconditioning, as described on p.34 of [Kresse1996]_ """
[docs] def __init__(self, pm): """Initializes variables parameters ---------- pm: object input parameters """ self.x_npt = pm.sys.grid self.x_delta = pm.sys.deltax # eqn (82) self.A = pm.lda.mix #kerker_length: float # screening distance for Kerker preconditioning [a0] # Default corresponds to :math:`2\pi/\lambda = 1.5\AA` self.q0 = 2*np.pi / pm.lda.kerker_length dq = 2*np.pi / (2 * pm.sys.xmax) q0_scaled = self.q0/ dq self.G_q = np.zeros((self.x_npt//2+1), dtype=np.float) for q in range(len(self.G_q)): self.G_q[q] = self.A * q**2 / (q**2 + q0_scaled**2) #self.G_r = np.set_diagonal(diagonal # one-dimensional Kerker mixing a = pm.sys.acon q = dq* np.array(list(range(self.x_npt//2+1))) aq = np.abs(a*q) Si, Ci = scsp.sici(aq) # verified that this agrees with Mathematica... v_k = -2*(np.cos(aq)*Ci + np.sin(aq)*(Si-np.pi/2)) self.G_q_1d = self.A * 1/(1 + v_k * self.q0)
[docs] def precondition(self, f, eigv, eigf): """Return preconditioned f""" f = np.fft.rfft(f, n=self.x_npt) f *= self.G_q f = np.fft.irfft(f, n=self.x_npt) return f
[docs]class RPAPreconditioner(object): """Performs preconditioning using RPA dielectric function The static dielectric function as a function of x and x' is computed in the Hartree approximation. """
[docs] def __init__(self, pm): """Initializes variables parameters ---------- pm: object input parameters """ self.x_npt = pm.sys.grid self.x_delta = pm.sys.deltax self.NE = pm.sys.NE self.pm = pm # set up coulomb repulsion matrix v(i,j) tmp = np.empty((self.x_npt, self.x_npt), dtype=int) for i in range(self.x_npt): for j in range(self.x_npt): tmp[i,j] = np.abs(i - j) self.coulomb_repulsion = 1.0/(tmp * self.x_delta + pm.sys.acon)
[docs] def precondition(self, r, eigv, eigf): r"""Preconditioning using RPA dielectric matrix .. math :: \frac{\delta V(x)}{\delta \rho(x')} = DXC(x) \delta(x-x') + v(x-x') parameters ---------- r: array_like array of residuals to be preconditioned eigv: array_like array of eigenvalues eigf: array_like array of eigenfunctions """ dx = self.x_delta nx = self.x_npt v = self.coulomb_repulsion n = np.sum(np.abs(eigf[:self.NE])**2, axis=0) M = np.zeros((nx,nx)) # note: this circular dependency only works in python 2.x # for python 3 let the constructur take the DXC function # as a parameter (see e.g. mix.py for examples of this) from . import LDA np.fill_diagonal(M, LDA.DXC(self.pm, n)/dx) M += v chi = self.chi(eigv,eigf) # this is the correct recipe for *density* mixing. eps = np.eye(nx)/dx - np.dot(chi,M)*dx # for *potential* mixing use #eps = np.eye(nx)/dx - np.dot(M,chi)*dx epsinv = np.linalg.inv(eps)/dx**2 r = np.dot(epsinv, r.T) * dx return r.T
[docs] def chi(self,eigv, eigf): r"""Computes RPA polarizability The static, non-local polarisability (aka density-potential response) in the Hartree approximation (often called RPA): .. math :: \chi^0(x,x') = \sum_j^{'} \sum_k^{''} \phi_j(x)\phi_k^*(x)\phi_j^*(x')\phi_k(x') \frac{2}{\varepsilon_j-\varepsilon_k} where :math:`\sum^{'}` sums over occupied states and :math:`\sum^{''}` sums over empty states See also https://wiki.fysik.dtu.dk/gpaw/documentation/tddft/dielectric_response.html parameters ---------- eigv: array_like array of eigenvalues eigf: array_like array of eigenfunctions returns ------- epsilon: array_like dielectric matrix in real space """ N = np.minimum(len(eigv), 10*self.NE) nx = self.x_npt chi = np.zeros((nx,nx)) #for j in range(0,self.NE): # for k in range(self.NE,N): # for ix1 in range(self.x_npt): # eps[ix1, :] += eigf[j,ix1]*np.conj(eigf[k,ix1])*np.conj(eigf[j,:])*eigf[k,:] *2.0/(eigv[j] - eigv[k]) # eigenvalues should anyhow be real... eigv = eigv.real for j in range(0,self.NE): for k in range(self.NE,N): p1 = eigf[j] * np.conj(eigf[k]) p2 = np.conj(p1) tmp = np.outer(p1,p2) chi += tmp.real * 2.0/(eigv[j] - eigv[k]) #if j==self.NE-1 and k==self.NE: # print("") # print('{:.3e}'.format(eigv[j] - eigv[k])) #eps = np.dot(self.coulomb_repulsion,eps)*self.x_delta #eps = np.eye(nx)/self.x_delta - eps return chi