Source code for iDEA.HF

"""Computes ground-state and time-dependent charge density in the Hartree-Fock approximation.
The code outputs the ground-state charge density, the energy of the system and
the Hartree-Fock orbitals.
"""
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
import copy
import pickle
import numpy as np
import scipy as sp
import scipy.linalg as spla
import scipy.sparse as sps
from . import results as rs
from . import RE_cython


[docs]def hartree(pm, density): r"""Computes Hartree potential for a given density .. math:: v_{\text{H}}(x) = \int \! n(y)u(x,y) \, \mathrm{d}y parameters ---------- pm : object Parameters object density : array_like given density returns array_like Hartree potential """ return np.dot(pm.space.v_int, density)*pm.space.delta
[docs]def fock(pm, eigf): r"""Constructs Fock operator from a set of orbitals .. math:: \Sigma_{\text{x}}(x,y) = - \sum_{j=1}^{N} \varphi_{j}^{*}(y) \varphi_{j}(x) u(x,y) where u(x,y) denotes the appropriate Coulomb interaction. parameters ---------- pm : object Parameters object eigf : array_like Eigenfunction orbitals returns ------- F: array_like Fock matrix """ F = np.zeros((pm.sys.grid,pm.sys.grid), dtype='complex') for i in range(pm.sys.NE): orb = eigf[:,i] F -= np.tensordot(orb, orb.conj(), axes=0) F = F * pm.space.v_int return F
[docs]def electron_density(pm, orbitals): r"""Compute density for given orbitals parameters ---------- pm : object Parameters object orbitals: array_like Array of properly normalised orbitals returns ------- n: array_like electron density """ occupied = orbitals[:, :pm.sys.NE] n = np.sum(occupied*occupied.conj(), axis=1) return n.real
[docs]def hamiltonian(pm, wfs, perturb=False): r"""Compute HF Hamiltonian Computes HF Hamiltonian from a given set of single-particle states parameters ---------- pm : object Parameters object wfs array_like single-particle states perturb: bool If True, add perturbation to external potential (for time-dep. runs) returns array_like Hamiltonian matrix """ # Construct kinetic energy sd = pm.space.second_derivative sd_ind = pm.space.second_derivative_indices T = -0.5*sps.diags(sd, sd_ind, shape=(pm.sys.grid,pm.sys.grid), format='csr', dtype=complex).toarray() # Construct potentials if perturb: Vext = np.diag(pm.space.v_ext + pm.space.v_pert) else: Vext = np.diag(pm.space.v_ext) Vh = np.diag(hartree(pm, electron_density(pm, wfs))) if pm.hf.fock == 1: F = fock(pm,wfs) else: F = np.zeros(shape=Vh.shape) # Construct H H = T + Vext + Vh + F*pm.space.delta return H
[docs]def groundstate(pm, H): r"""Diagonalises Hamiltonian H .. math:: \hat{H}\varphi_{j} = \varepsilon_{j}\varphi_{j} parameters ---------- pm: object Parameters object H: array_like Hamiltonian matrix (band form) returns ------- n: array_like density eigf: array_like normalised orbitals, index as eigf[space_index,orbital_number] eigv: array_like orbital energies """ # Solve eigen equation eigv, eigf = spla.eigh(H) eigf = eigf/ np.sqrt(pm.sys.deltax) # Calculate density n = electron_density(pm,eigf) return n, eigf, eigv
[docs]def total_energy(pm, eigf, eigv): r"""Calculates total energy of Hartree-Fock wave function parameters ---------- pm : array_like external potential eigf : array_like eigenfunctions eigv : array_like eigenvalues returns float """ E_HF = 0 E_HF += np.sum(eigv[:pm.sys.NE]) # Subtract Hartree energy n = electron_density(pm, eigf) V_H = hartree(pm, n) E_HF -= 0.5 * np.dot(V_H,n) * pm.sys.deltax # Fock correction F = fock(pm,eigf) for k in range(pm.sys.NE): orb = eigf[:,k] E_HF -= 0.5 * np.dot(orb.conj().T, np.dot(F, orb)) * pm.sys.deltax**2 return E_HF.real
[docs]def calculate_current_density(pm, density): r"""Calculates the current density from the time-dependent (and ground-state) electron density by solving the continuity equation. .. math:: \frac{\partial n}{\partial t} + \nabla \cdot j = 0 parameters ---------- pm : object Parameters object density : array_like 2D array of the time-dependent density, indexed as density[time_index,space_index] returns array_like 2D array of the current density, indexed as current_density[time_index,space_index] """ pm.sprint('', 1, newline=True) current_density = np.zeros((pm.sys.imax,pm.sys.grid), dtype=np.float) string = 'HF: calculating current density' pm.sprint(string, 1, newline=True) for i in range(1, pm.sys.imax): string = 'HF: t = {:.5f}'.format(i*pm.sys.deltat) pm.sprint(string, 1, newline=False) J = np.zeros(pm.sys.grid, dtype=np.float) J = RE_cython.continuity_eqn(pm, density[i,:], density[i-1,:]) current_density[i,:] = J[:] pm.sprint('', 1, newline=True) return current_density
[docs]def crank_nicolson_step(pm, waves, H): r"""Solves Crank Nicolson Equation .. math:: \left(\mathbb{1} + i\frac{dt}{2} H\right) \Psi(x,t+dt) = \left(\mathbb{1} - i \frac{dt}{2} H\right) \Psi(x,t) for :math:`\Psi(x,t+dt)`. parameters ---------- pm : object Parameters object total_td_density : array_like Time dependent density of the system indexed as total_td_density[time_index][space_index] returns array_like Time dependent current density indexed as current_density[time_index][space_index] """ # Construct matrices dH = 0.5J * pm.sys.deltat * H identity = np.eye(pm.sys.grid, dtype=np.complex) A = identity + dH Abar = identity - dH # Solve for all single-particle states at once RHS = np.dot(Abar, waves[:, :pm.sys.NE]) waves_new = spla.solve(A,RHS) return waves_new
[docs]def main(parameters): r"""Performs Hartree-fock calculation parameters ---------- parameters : object Parameters object returns object Results object """ pm = parameters pm.setup_space() # Take external potential for initial guess # (setting wave functions to zero yields V=V_ext) waves = np.zeros((pm.sys.grid,pm.sys.NE), dtype=np.complex) H = hamiltonian(pm, waves) den,eigf,eigv = groundstate(pm, H) # Calculate ground state density converged = False iteration = 1 while (not converged): # Calculate new potentials form new orbitals H_new = hamiltonian(pm, eigf) # Stability mixing H_new = (1-pm.hf.nu)*H + pm.hf.nu*H_new # Diagonalise Hamiltonian den_new, eigf, eigv = groundstate(pm, H_new) dn = np.sum(np.abs(den-den_new))*pm.sys.deltax converged = dn < pm.hf.con E_HF = total_energy(pm, eigf, eigv) s = 'HF: E = {:+.8f} Ha, dn = {:+.3e}, iter = {}'\ .format(E_HF, dn, iteration) pm.sprint(s, 1, newline=False) # save for next iteration iteration += 1 H = H_new den = den_new pm.sprint() # Calculate ground state energy pm.sprint('HF: hartree-fock energy = {}'.format(E_HF.real), 1, newline=True) # Construct results object results = rs.Results() results.add(E_HF,'gs_hf_E') results.add(-eigv[pm.sys.NE-1],'gs_hf_IP') results.add(-eigv[pm.sys.NE],'gs_hf_AF') results.add(eigv[pm.sys.NE]-eigv[pm.sys.NE-1],'gs_hf_GAP') results.add(den,'gs_hf_den') results.add(hartree(pm, den),'gs_hf_vh') results.add(fock(pm, eigf),'gs_hf_F') results.add(eigf.T, 'gs_hf_eigf') results.add(eigv, 'gs_hf_eigv') # Save results if pm.run.save: results.save(pm) if pm.run.time_dependence: # Starting values for wave functions, density waves[:, :pm.sys.NE] = eigf[:, :pm.sys.NE] n_t = np.empty((pm.sys.imax, pm.sys.grid), dtype=np.float) F_t = np.empty((pm.sys.imax, pm.sys.grid, pm.sys.grid), dtype=np.complex) Vh_t = np.empty((pm.sys.imax, pm.sys.grid) , dtype=np.float) n_t[0] = den F_t[0,:,:] = fock(pm, waves) Vh_t[0,:] = hartree(pm, den) # Perform time evolution for i in range(1, pm.sys.imax): string = 'HF: evolving through real time: t = {:.4f}'.format(i*pm.sys.deltat) pm.sprint(string, 1, newline=False) H = hamiltonian(pm, waves, perturb=True) waves[:, :pm.sys.NE] = crank_nicolson_step(pm, waves, H) S = np.dot(waves.T.conj(), waves) * pm.sys.deltax orthogonal = np.allclose(S, np.eye(pm.sys.NE, dtype=np.complex),atol=1e-6) if not orthogonal: pm.sprint("HF: Warning: Orthonormality of orbitals violated at iteration {}".format(i+1)) den = electron_density(pm, waves) n_t[i] = den F_t[i,:,:] = fock(pm, waves) Vh_t[i,:] = hartree(pm, den) pm.sprint() # Calculate the current density current_density = calculate_current_density(pm, n_t) # Output results pm.sprint('HF: saving quantities...', 1, newline=True) results.add(n_t, 'td_hf_den') results.add(F_t, 'td_hf_F') results.add(Vh_t, 'td_hf_vh') results.add(current_density, 'td_hf_cur') if pm.run.save: l = ['td_hf_den','td_hf_cur', 'td_hf_F', 'td_hf_vh'] results.save(pm, list=l) return results