Coverage for gpaw/test/test_scf_criteria.py: 90%

81 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-07-19 00:19 +0000

1import numpy as np 

2import pytest 

3 

4from ase import Atoms 

5from ase.units import Ha 

6 

7from gpaw import GPAW 

8from gpaw.convergence_criteria import WorkFunction, Energy, Criterion 

9 

10 

11class FourIterations(Criterion): 

12 """A silly custom convergence criterion that ensures it runs 

13 at least four iterations.""" 

14 name = 'four iterations' 

15 tablename = 'four' 

16 calc_last = False 

17 

18 def __init__(self): 

19 self.description = 'At least four iterations must complete!' 

20 self.reset() 

21 

22 def __call__(self, context): 

23 converged = self.iters >= 4 

24 entry = str(self.iters) 

25 self.iters += 1 

26 return converged, entry 

27 

28 def reset(self): 

29 self.iters = 0 

30 

31 

32@pytest.mark.parametrize('gpaw_mode', ['fd', 'lcao']) 

33def test_scf_criterion(in_tmp_dir, gpaw_new, gpaw_mode): 

34 """Tests different ways of setting SCF convergence criteria, 

35 and that it behaves consistenly with regard to the work function.""" 

36 convergence = {'eigenstates': 1.0, 

37 'density': 1.0, 

38 'energy': 1.0, 

39 'work function': 1.0} 

40 if gpaw_new: 

41 convergence['eigenvalues'] = 1.0 

42 atoms = Atoms('HF', [(0., 0.5, 0.5), 

43 (0., 0.4, -0.4)], 

44 cell=(5., 5., 9.), 

45 pbc=(True, True, False)) 

46 atoms.center() 

47 atoms.calc = GPAW(mode=gpaw_mode, 

48 basis='sz(dzp)', 

49 h=0.3, 

50 nbands=-1, 

51 convergence=convergence, 

52 txt=None, 

53 poissonsolver={'dipolelayer': 'xy'}) 

54 atoms.get_potential_energy() 

55 workfunctions1 = ( 

56 atoms.calc.dft.workfunctions() 

57 if gpaw_new else 

58 Ha * atoms.calc.hamiltonian.get_workfunctions(atoms.calc.wfs)) 

59 atoms.calc.write('scf-criterion.gpw') 

60 

61 # Flip and use saved calculator; work functions should be opposite. 

62 atoms = Atoms('HF', [(0., 0.5, -0.5), 

63 (0., 0.4, +0.4)], 

64 cell=(5., 5., 9.), 

65 pbc=(True, True, False)) 

66 atoms.center() 

67 if gpaw_new and gpaw_mode == 'lcao': 

68 # XXX: Hotfix due to lcao mode in GPAW new being buggy. 

69 # See https://gitlab.com/gpaw/gpaw/-/issues/1354 

70 atoms.calc = GPAW(mode=gpaw_mode, 

71 basis='sz(dzp)', 

72 h=0.3, 

73 nbands=-1, 

74 convergence=convergence, 

75 txt=None, 

76 poissonsolver={'dipolelayer': 'xy'}) 

77 else: 

78 atoms.calc = GPAW('scf-criterion.gpw') # checks loading 

79 atoms.get_potential_energy() 

80 workfunctions2 = ( 

81 atoms.calc.dft.workfunctions() 

82 if gpaw_new else 

83 Ha * atoms.calc.hamiltonian.get_workfunctions(atoms.calc.wfs)) 

84 

85 assert workfunctions1[0] == pytest.approx(workfunctions2[1]) 

86 assert workfunctions1[1] == pytest.approx(workfunctions2[0]) 

87 if gpaw_new: 

88 assert atoms.calc.dft.scf_loop.convergence['work function'].tol == 1.0 

89 else: 

90 assert atoms.calc.scf.criteria['work function'].tol == 1.0 

91 

92 # Try import syntax, and verify it creates a new instance internally. 

93 workfunction = WorkFunction(0.5) 

94 convergence = {'eigenstates': 1.0, 

95 'density': 1.0, 

96 'energy': 1.0, 

97 'work function': workfunction} 

98 if gpaw_new: 

99 convergence['eigenvalues'] = 1.0 

100 atoms.calc = atoms.calc.new(convergence=convergence) 

101 atoms.get_potential_energy() 

102 if gpaw_new: 

103 cc = atoms.calc.dft.scf_loop.convergence 

104 else: 

105 cc = atoms.calc.scf.criteria 

106 assert cc['work function'] is not workfunction 

107 assert cc['work function'].tol == pytest.approx(0.5) 

108 

109 # Switch to H2 for faster calcs. 

110 for atom in atoms: 

111 atom.symbol = 'H' 

112 

113 # Change a default. 

114 convergence = {'energy': Energy(2.0, n_old=4), 

115 'density': np.inf, 

116 'eigenstates': np.inf} 

117 if gpaw_new: 

118 convergence['eigenvalues'] = np.inf 

119 atoms.calc = atoms.calc.new(convergence=convergence) 

120 atoms.get_potential_energy() 

121 if gpaw_new: 

122 assert atoms.calc.dft.scf_loop.convergence['energy'].n_old == 4 

123 else: 

124 assert atoms.calc.scf.criteria['energy'].n_old == 4 

125 

126 

127def test_scf_custom_criterion(in_tmp_dir): 

128 """Simulate a user creating their own custom convergence criterion, 

129 saving the .gpw file, and re-loading it. It will warn the user at two 

130 points.""" 

131 convergence = {'eigenstates': 1.0, 

132 'density': 1.0, 

133 'energy': 1.0, 

134 'custom': FourIterations()} 

135 

136 atoms = Atoms('HF', [(0., 0.5, 0.5), 

137 (0., 0.4, -0.4)], 

138 cell=(5., 5., 9.), 

139 pbc=(True, True, False)) 

140 atoms.center() 

141 atoms.rattle() 

142 calc = GPAW(mode='fd', 

143 h=0.3, 

144 nbands=-1, 

145 convergence=convergence, 

146 txt='out.txt', 

147 poissonsolver={'dipolelayer': 'xy'}) 

148 atoms.calc = calc 

149 with pytest.warns(UserWarning): 

150 # Warns the user that their criterion must be a unique instance, 

151 # and can't be saved/loaded. 

152 atoms.get_potential_energy() 

153 calc.write('four.gpw') 

154 with pytest.warns(UserWarning): 

155 # Warns the user that their criterion did not load. 

156 calc = GPAW('four.gpw', txt='out2.txt') 

157 atoms[1].x += 0.1 

158 atoms.calc = calc 

159 atoms.get_potential_energy() 

160 

161 

162if __name__ == '__main__': 

163 test_scf_criterion(1, 1)