Coverage for gpaw/test/test_reuse_wfs.py: 98%

48 statements  

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

1import pytest 

2from ase.build import bulk 

3 

4from gpaw import GPAW, Mixer 

5from gpaw.convergence_criteria import Eigenstates 

6 

7 

8class MyConvergenceCriterion(Eigenstates): 

9 def __init__(self, tol): 

10 super().__init__(tol) 

11 self.history = [] 

12 

13 def get_error(self, context): 

14 value = super().get_error(context) 

15 self.history.append(value) 

16 return value 

17 

18 

19def run(atoms, method, kwargs): 

20 conv_tol = 1e-9 

21 conv = MyConvergenceCriterion(conv_tol) 

22 

23 kwargs = { 

24 'convergence': {'custom': [conv]}, 

25 'experimental': {'reuse_wfs_method': method}, 

26 **kwargs} 

27 

28 calc = GPAW(**kwargs) 

29 

30 atoms.calc = calc 

31 with pytest.warns(UserWarning, 

32 match='Custom convergence criterion'): 

33 E1 = atoms.get_potential_energy() 

34 assert conv.history[-1] < conv_tol 

35 niter1 = len(conv.history) 

36 del conv.history[:] 

37 

38 atoms.rattle(stdev=0.0001) 

39 

40 if method is None and not calc.old: 

41 calc.dft.ibzwfs.move_wave_functions = lambda *args: None 

42 

43 E2 = atoms.get_potential_energy() 

44 niter2 = len(conv.history) 

45 reuse_error = conv.history[0] 

46 

47 # If the change in energy is exactly or suspiciously close to zero, it's 

48 # because nothing was done at all (something was cached but shouldn't 

49 # have been) 

50 delta_e = abs(E2 - E1) 

51 assert delta_e > 1e-6, delta_e 

52 return niter1, niter2, reuse_error 

53 

54 

55@pytest.mark.parametrize('mode, reuse_type, max_reuse_error', [ 

56 ('pw', 'paw', 1e-5), 

57 ('pw', None, 1e-4), 

58 ('fd', 'paw', 1e-4), 

59 ('fd', None, 1e-3)]) 

60def test_reuse_wfs(mode, reuse_type, max_reuse_error): 

61 """Check that wavefunctions are meaningfully reused. 

62 

63 For a different modes and parameters, this test asserts that the 

64 initial wavefunction error in the second scf step is below a 

65 certain threshold, indicating that we are doing better than if 

66 we started from scratch.""" 

67 

68 atoms = bulk('Si') 

69 atoms.rattle(stdev=0.01, seed=17) # Break symmetry 

70 

71 kwargs = dict( 

72 mode=mode, 

73 kpts=(2, 2, 2), 

74 xc='PBE', 

75 mixer=Mixer(0.4, 5, 20.0)) 

76 

77 niter1, niter2, reuse_error = run( 

78 atoms, reuse_type, kwargs) 

79 

80 # It should at the very least be faster to do the second step: 

81 assert niter2 < niter1 

82 assert reuse_error < max_reuse_error 

83 

84 

85# @pytest.mark.old_gpaw_only 

86def test_reuse_sg15(sg15_hydrogen): 

87 """Test wfs reuse with sg15. 

88 

89 As of writing this test, the sg15 pseudopotentials have no pseudo 

90 partial waves, and therefore, reusing them should have no effect.""" 

91 from ase.build import molecule 

92 atoms = molecule('H2', vacuum=2.0) 

93 

94 # If we do not rattle, we will get broken symmetry error: 

95 atoms.rattle(stdev=.001) 

96 

97 kwargs = dict( 

98 mode='pw', 

99 setups={'H': sg15_hydrogen}, 

100 xc='PBE') 

101 

102 niter1, niter2, reuse_error = run(atoms, 'paw', kwargs) 

103 assert niter2 < niter1 

104 assert reuse_error < 1e-5