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
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-19 00:19 +0000
1import numpy as np
2import pytest
4from ase import Atoms
5from ase.units import Ha
7from gpaw import GPAW
8from gpaw.convergence_criteria import WorkFunction, Energy, Criterion
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
18 def __init__(self):
19 self.description = 'At least four iterations must complete!'
20 self.reset()
22 def __call__(self, context):
23 converged = self.iters >= 4
24 entry = str(self.iters)
25 self.iters += 1
26 return converged, entry
28 def reset(self):
29 self.iters = 0
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')
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))
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
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)
109 # Switch to H2 for faster calcs.
110 for atom in atoms:
111 atom.symbol = 'H'
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
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()}
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()
162if __name__ == '__main__':
163 test_scf_criterion(1, 1)