Source code for iDEA.test_minimize

"""Tests for direct minimizers
""" 
from __future__ import division
from __future__ import absolute_import

import numpy as np
import numpy.testing as nt
import unittest
import scipy.sparse as sps
import scipy.linalg as spla

from . import input
from . import minimize
from . import LDA

machine_epsilon = np.finfo(float).eps

[docs]class TestCG(unittest.TestCase): """Tests for the conjugate gradient minimizer """
[docs] def setUp(self): """ Sets up harmonic oscillator system """ pm = input.Input() pm.run.name = 'unittest' pm.run.save = False pm.run.verbosity = 'low' pm.lda.NE = 2 self.pm = pm
[docs] def test_steepest_dirs(self): """Testing orthogonalisation in steepest descent Just checking that the efficient numpy routines do the same as more straightforward loop-based techniques """ pm = self.pm minimizer = minimize.CGMinimizer(pm, total_energy='dummy') # prepare Hamiltonian sys = pm.sys T = -0.5*sps.diags([1, -2, 1],[-1, 0, 1], shape=(sys.grid,sys.grid), format='csr')/(sys.deltax**2) x = np.linspace(-sys.xmax,sys.xmax,sys.grid) V = sps.diags(sys.v_ext(x), 0, shape=(sys.grid, sys.grid), dtype=np.float, format='csr') H = (T+V).toarray() energies, wfs = spla.eigh(H) steepest_orth_all = minimizer.steepest_dirs(wfs, H) # repeat orthogonalization for one single wave function i = 2 # could be any other state as well wf = wfs[:,i] steepest = -(np.dot(H,wf) - energies[i] * wf) overlaps = np.dot(wfs.conj(), steepest) steepest_orth = steepest for j in range(len(wfs)): if j != i: steepest_orth -= overlaps[j] * wfs[j] nt.assert_almost_equal(steepest_orth_all[i], steepest_orth) # masked variant overlaps = np.ma.array(np.dot(wfs.conj().T, steepest), mask=False) overlaps.mask[i] = True steepest_orth = steepest - np.ma.dot(overlaps.T, wfs) steepest_orth = np.ma.getdata(steepest_orth) nt.assert_almost_equal(steepest_orth_all[i], steepest_orth) # unmasked variant overlaps = np.dot(wfs.conj().T, steepest) steepest_orth = steepest - np.dot(overlaps.T, wfs) nt.assert_almost_equal(steepest_orth_all[i], steepest_orth)
[docs] def test_conjugate(self): r"""Check that gradient is computed correctly Use conjugate-gradient method on a quadratic function in n-dimensional space, where it is guaranteed to converge in n steps. Note: This exact condition actually doesn't apply here because we are orthonormalising the vectors (i.e. rotating) after each step. This renders this test a bit pointless... I am currently taking 2*n steps and am still far from machine precision. Task: minimize the function .. math :: E(v) = \sum_{i=1}^2 v_i^* H v_i \triangle_{v_i} E = H v_i for a fixed matrix H and a set of orthonormal vectors v_i. Note: The dimension n of the vector space is the grid spacing times number of electrons. """ pm = self.pm pm.sys.grid = 10 sqdx = np.sqrt(pm.sys.deltax) pm.setup_space() ndim = pm.sys.grid * pm.sys.NE E = lambda wfs: (wfs.conj() * np.dot(H, wfs)).sum() E_scaled = lambda pm, wfs: E(wfs*sqdx) # do not restart the cg minimizer = minimize.CGMinimizer(pm, total_energy=E_scaled, cg_restart=ndim+1, line_fit='quadratic-der') # start with random guess np.random.seed(1) wfs = np.random.rand(pm.sys.grid,pm.sys.NE) wfs = minimize.orthonormalize(wfs) H = LDA.banded_to_full(pm, H=LDA.hamiltonian(pm, orbitals=wfs/sqdx)) # keeping H constant, we should find the minimum in ndim steps # (if it weren't for the orthonormality condition!...) for i in range(2*ndim): wfs = minimizer.step(wfs/sqdx,H) * sqdx E_1 = E(wfs) # compute lowest energy using diagonalisation energies_2, wfs_2 = spla.eigh(H) E_2 = np.sum(energies_2[:pm.sys.NE]) nt.assert_allclose(E_1, E_2, rtol=1e-7)
[docs] def test_orthonormalisation(self): """Testing orthonormalisation of a set of vectors minimize. This should do the same as Gram-Schmidt """ m = 8 # dimension of space n = 4 # number of vectors vecs = np.random.rand(m,n) q = np.empty((m,n)) # the regular Gram-Schmidt algorithm for i in range(n): v = vecs[:,i] for j in range(i): v -= np.dot(v,q[:,j]) / np.dot(q[:,j],q[:,j]) * q[:,j] v /= np.linalg.norm(v) q[:,i] = v # is q an orthogonal matrix? nt.assert_almost_equal(np.dot(q.T,q), np.identity(n)) q2 = minimize.orthonormalize(vecs) # is q2 an orthogonal matrix? nt.assert_almost_equal(np.dot(q2.T, q2), np.identity(n)) # are q and q2 the same? nt.assert_almost_equal(q, q2)
[docs]class TestCGLDA(unittest.TestCase): """Tests for the conjugate gradient minimizer On an actual LDA system """
[docs] def setUp(self): """ Sets up harmonic oscillator system """ pm = input.Input() pm.run.name = 'unittest' pm.run.save = False pm.run.verbosity = 'low' # It might still be possible to speed this up pm.sys.NE = 2 #: Number of electrons pm.sys.grid = 61 #: Number of grid points (must be odd) pm.sys.xmax = 7.5 #: Size of the system pm.sys.acon = 1.0 #: Smoothing of the Coloumb interaction pm.sys.interaction_strength = 1#: Scales the strength of the Coulomb interaction def v_ext(x): """Initial external potential""" return 0.5*(0.25**2)*(x**2) pm.sys.v_ext = v_ext pm.lda.NE = 2 pm.lda.scf_type = 'cg' self.pm = pm
[docs] def test_energy_derivative(self): r"""Compare analytical total energy derivative vs finite differences .. math:: \frac{dE}{d\lambda}(\lambda) \approx \frac{E(\lambda)+E(\lambda+\delta)}{\delta} """ pm = self.pm pm.lda.max_iter = 5 # just start, don't solve it perfectly results = LDA.main(pm) # solving using linear mixing vks = results.gs_lda2_vks wfs = results.gs_lda2_eigf[:pm.sys.NE].T * np.sqrt(pm.sys.deltax) H = LDA.banded_to_full(pm, H=LDA.hamiltonian(pm, v_ks=vks)) # compute current conjugate direction minimizer = minimize.CGMinimizer(pm, total_energy=LDA.total_energy_eigf) steepest = minimizer.steepest_dirs(wfs, H) conjugate = minimizer.conjugate_directions(steepest, wfs) dE_dL = 2 * np.sum(minimizer.braket(conjugate, H, wfs)) # compute finite difference derivative delta = 1.0e-6 E_1 = minimizer.total_energy(wfs) wfs_new = minimize.orthonormalize(wfs + delta * conjugate) E_2 = minimizer.total_energy(wfs_new) dE_dL_finite = (E_2 - E_1)/ delta wfs_new = minimize.orthonormalize(wfs + delta * conjugate) # this can be quite close to machine epsilon # for the energy difference atol = np.abs(E_1) * 100 * machine_epsilon / delta rtol = atol/ np.abs(dE_dL_finite) nt.assert_allclose(dE_dL, dE_dL_finite, rtol=rtol)