Coverage for gpaw/test/conftest.py: 84%
146 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-08 00:17 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-08 00:17 +0000
1import os
2from contextlib import contextmanager
3from functools import cached_property
5import numpy as np
6import pytest
7from gpaw import setup_paths, GPAW_NEW
8from gpaw.cli.info import info
9from gpaw.mpi import broadcast, world
10from gpaw.test.gpwfile import GPWFiles, _all_gpw_methodnames
11from gpaw.test.mmefile import MMEFiles
12from gpaw.utilities import devnull
13import subprocess
16@contextmanager
17def execute_in_tmp_path(request, tmp_path_factory):
18 if world.rank == 0:
19 # Obtain basename as
20 # * request.function.__name__ for function fixture
21 # * request.module.__name__ for module fixture
22 basename = getattr(request, request.scope).__name__
23 path = tmp_path_factory.mktemp(basename)
24 else:
25 path = None
26 path = broadcast(path)
27 cwd = os.getcwd()
28 os.chdir(path)
29 try:
30 yield path
31 finally:
32 os.chdir(cwd)
35@pytest.fixture(scope='module')
36def dftd3():
37 from ase.calculators.dftd3 import PureDFTD3
38 try:
39 subprocess.call(PureDFTD3().command)
40 except FileNotFoundError:
41 pytest.skip('dftd3 command not found')
44@pytest.fixture(scope='function')
45def in_tmp_dir(request, tmp_path_factory):
46 """Run test function in a temporary directory."""
47 with execute_in_tmp_path(request, tmp_path_factory) as path:
48 yield path
51@pytest.fixture(scope='module')
52def module_tmp_path(request, tmp_path_factory):
53 """Run test module in a temporary directory."""
54 with execute_in_tmp_path(request, tmp_path_factory) as path:
55 yield path
58@pytest.fixture
59def add_cwd_to_setup_paths():
60 """Temporarily add current working directory to setup_paths."""
61 try:
62 setup_paths[:0] = ['.']
63 yield
64 finally:
65 del setup_paths[:1]
68@pytest.fixture(scope='session')
69def sessionscoped_monkeypatch():
70 # The standard monkeypatch fixture is function scoped
71 # so we need to roll our own
72 with pytest.MonkeyPatch.context() as monkeypatch:
73 yield monkeypatch
76@pytest.fixture(autouse=True, scope='session')
77def monkeypatch_response_spline_points(sessionscoped_monkeypatch):
78 import gpaw.response.paw as paw
79 # https://gitlab.com/gpaw/gpaw/-/issues/984
80 sessionscoped_monkeypatch.setattr(paw, 'DEFAULT_RADIAL_POINTS', 2**10)
83@pytest.fixture(autouse=True, scope='session')
84def monkeypatch_allow_cpupy(sessionscoped_monkeypatch):
85 """Monkey-patch `gpaw.new.builder.DFTComponentsBuilder.gpu` to
86 allow setting `gpu` regardless of the value of `GPAW_CPUPY`.
87 """
88 import gpaw
89 from gpaw.new.builder import DFTComponentsBuilder
91 @cached_property
92 def gpu(self) -> bool:
93 return self.params.parallel.get('gpu', gpaw.GPAW_USE_GPUS)
95 sessionscoped_monkeypatch.setattr(DFTComponentsBuilder, 'gpu', gpu)
96 # Needed for `@cached_property` to work
97 gpu.__set_name__(DFTComponentsBuilder, 'gpu')
100@pytest.fixture(scope='session')
101def gpw_files(request):
102 """Reuse gpw-files.
104 Returns a dict mapping names to paths to gpw-files.
105 The files are written to the pytest cache and can be cleared using
106 pytest --cache-clear.
108 Example::
110 def test_something(gpw_files):
111 calc = GPAW(gpw_files['h2_lcao'])
112 ...
114 Possible systems are:
116 * Bulk BCC-Li with 3x3x3 k-points: ``bcc_li_pw``, ``bcc_li_fd``,
117 ``bcc_li_lcao``.
119 * O2 molecule: ``o2_pw``.
121 * H2 molecule: ``h2_pw``, ``h2_fd``, ``h2_lcao``.
123 * H2 molecule (not centered): ``h2_pw_0``.
125 * N2 molecule ``n2_pw``
127 * N molecule ``n_pw``
129 * Spin-polarized H atom: ``h_pw``.
131 * Polyethylene chain. One unit, 3 k-points, no symmetry:
132 ``c2h4_pw_nosym``. Three units: ``c6h12_pw``.
134 * Bulk BN (zinkblende) with 2x2x2 k-points and 9 converged bands:
135 ``bn_pw``.
137 * h-BN layer with 3x3x1 (gamma center) k-points and 26 converged bands:
138 ``hbn_pw``.
140 * Graphene with 6x6x1 k-points: ``graphene_pw``
142 * I2Sb2 (Z2 topological insulator) with 6x6x1 k-points and no
143 symmetries: ``i2sb2_pw_nosym``
145 * MoS2 with 6x6x1 k-points: ``mos2_pw`` and ``mos2_pw_nosym``
147 * MoS2 with 5x5x1 k-points: ``mos2_5x5_pw``
149 * NiCl2 with 6x6x1 k-points: ``nicl2_pw`` and ``nicl2_pw_evac``
151 * V2Br4 (AFM monolayer), LDA, 4x2x1 k-points, 28(+1) converged bands:
152 ``v2br4_pw`` and ``v2br4_pw_nosym``
154 * Bulk Si, LDA, 2x2x2 k-points (gamma centered): ``si_pw``
156 * Bulk Si, LDA, 4x4x4 k-points, 8(+1) converged bands: ``fancy_si_pw``
157 and ``fancy_si_pw_nosym``
159 * Bulk SiC, LDA, 4x4x4 k-points, 8(+1) converged bands: ``sic_pw``
160 and ``sic_pw_spinpol``
162 * Bulk Fe, LDA, 4x4x4 k-points, 9(+1) converged bands: ``fe_pw``
163 and ``fe_pw_nosym``
165 * Bulk C, LDA, 2x2x2 k-points (gamma centered), ``c_pw``
167 * Bulk Co (HCP), 4x4x4 k-points, 12(+1) converged bands: ``co_pw``
168 and ``co_pw_nosym``
170 * Bulk SrVO3 (SC), 3x3x3 k-points, 20(+1) converged bands: ``srvo3_pw``
171 and ``srvo3_pw_nosym``
173 * Bulk Al, LDA, 4x4x4 k-points, 10(+1) converged bands: ``al_pw``
174 and ``al_pw_nosym``
176 * Bulk Al, LDA, 4x4x4 k-points, 4 converged bands: ``bse_al``
178 * Bulk Ag, LDA, 2x2x2 k-points, 6 converged bands,
179 2eV U on d-band: ``ag_pw``
181 * Bulk GaAs, LDA, 4x4x4 k-points, 8(+1) bands converged: ``gaas_pw``
182 and ``gaas_pw_nosym``
184 * Bulk P4, LDA, 4x4 k-points, 40 bands converged: ``p4_pw``
186 * Distorted bulk Fe, revTPSS: ``fe_pw_distorted``
188 * Distorted bulk Si, TPSS: ``si_pw_distorted``
190 * C2H4 molecule (ethene) with direct optimization,
191 in with finite difference: ``c2h4_do_fd``
193 * C2H4 molecule (ethene) with direct optimization with
194 plane wave mode: ``c2h4_do_pw``
196 * H3 molecule, numerical, plane wave,
197 complex: ``h3_do_num_pw_complex``
199 * H3 molecule, numerical, PW: ``h3_do_num_pw``
201 * H3 molecule, steepest descent, LCAO: ``h3_do_sd_lcao``
203 * H3 molecule, numerical, LCAO: ``h3_do_num_lcao``
205 * H2O molecule, GMF, LCAO: ``h2o_do_gmf_lcao``
207 * H2O molecule, LCAO: ``h2o_do_lcao``
209 * H2O molecule, constrained direct optimization,
210 LCAO: ``h2o_cdo_lcao``
212 * H2O molecule, constrained direct optimization,
213 LCAO, SIC: ``h2o_cdo_lcao_sic``
215 * H2O molecule, FD, SIC: ``h2o_fdsic``
217 * H2O molecule, LCAO, SIC: ``h2o_lcaosic``
219 * H2O molecule, MOM, LCAO, SIC: ``h2o_mom_lcaosic``
221 * H2O molecule, GMF, LCAO, SIC: ``h2o_gmf_lcaosic``
223 * H2O molecule, MOM, PW, SIC: ``h2o_mom_pwsic``
225 * H2O molecule, PW, SIC: ``h2o_pwsic``
227 * H2O molecule, MOM, direct optimization, PW: ``h2o_mom_do_pw``
229 * CO molecule, MOM, direct optimization,
230 LCAO: ``co_mom_do_lcao_forces``
232 * H2O molecule, MOM, direct optimization, LCAO: ``h2o_mom_do_lcao``
234 * H2O molecule, PZ localization, PW: ``h2o_pz_localization_pw``
236 * C2H4 molecule (ethene), direct optimization, LCAO: ``c2h4_do_lcao``
238 * H3 molecule, orthonorm, LCAO: ``h3_orthonorm_lcao``
240 * H2 molecule, SIC, SCFSIC: ``h2_sic_scfsic``
242 * H atom with magnetic moment: ``h_magmom``
244 * H atom, hessian, numerical, PW: ``h_hess_num_pw``
246 * H2 molecule breaking, iLCAO: ``h2_break_ilcao``
248 * H atom, generalized davidson, LCAO: ``h_do_gdavid_lcao``
250 * H2 molecule, MOM, direct optimization, PWH: ``h2_mom_do_pwh``
252 * H atom, hessian, numerical, LCAO: ``h_hess_num_lcao``
254 Files always include wave functions.
255 """
256 cache = request.config.cache
257 gpaw_cachedir = cache.mkdir('gpaw_test_gpwfiles')
259 gpwfiles = GPWFiles(gpaw_cachedir)
261 try:
262 setup_paths.append(gpwfiles.testing_setup_path)
263 yield gpwfiles
264 finally:
265 setup_paths.remove(gpwfiles.testing_setup_path)
268@pytest.fixture(scope='session', params=sorted(_all_gpw_methodnames))
269def all_gpw_files(request, gpw_files, pytestconfig):
270 """This fixture parametrizes a test over all gpw_files.
272 For example pytest test_generate_gpwfiles.py -n 16 is a way to quickly
273 generate all gpw files independently of the rest of the test suite."""
275 # Note: Parametrizing over _all_gpw_methodnames must happen *after*
276 # it is populated, i.e., further down in the file than
277 # the @gpwfile decorator.
279 # TODO This xfail-information should probably live closer to the
280 # gpwfile definitions and not here in the fixture.
281 skip_if_new = {'Cu3Au_qna',
282 'nicl2_pw', 'nicl2_pw_evac',
283 'v2br4_pw', 'v2br4_pw_nosym',
284 'sih4_xc_gllbsc_fd', 'sih4_xc_gllbsc_lcao',
285 'na2_isolated', 'h2o_xas'}
286 if GPAW_NEW and request.param in skip_if_new:
287 pytest.xfail(f'{request.param} gpwfile not yet working with GPAW_NEW')
289 if request.param == 'Tl_box_pw' and world.size > 1:
290 pytest.skip(f'{request.param} gpwfile only works in serial')
292 # Accessing each file via __getitem__ executes the calculation:
293 return gpw_files[request.param]
296@pytest.fixture(scope='session')
297def mme_files(request, gpw_files):
298 """Reuse mme files"""
299 cache = request.config.cache
300 mme_cachedir = cache.mkdir('gpaw_test_mmefiles')
302 return MMEFiles(mme_cachedir, gpw_files)
305class GPAWPlugin:
306 def __init__(self):
307 if world.rank == -1:
308 print()
309 info()
311 def pytest_terminal_summary(self, terminalreporter, exitstatus, config):
312 from gpaw.mpi import size
313 terminalreporter.section('GPAW-MPI stuff')
314 terminalreporter.write(f'size: {size}\n')
317@pytest.fixture
318def sg15_hydrogen():
319 from io import StringIO
320 from gpaw.test.pseudopotential.H_sg15 import pp_text
321 from gpaw.upf import read_sg15
322 # We can't easily load a non-python file from the test suite.
323 # Therefore we load the pseudopotential from a Python file.
324 return read_sg15(StringIO(pp_text))
327def pytest_configure(config):
328 if world.rank != 0:
329 try:
330 tw = config.get_terminal_writer()
331 except (AssertionError, AttributeError):
332 pass
333 else:
334 tw._file = devnull
335 config.pluginmanager.register(GPAWPlugin(), 'pytest_gpaw')
338def pytest_runtest_setup(item):
339 """Skip some tests.
341 If:
343 * they depend on libxc and GPAW is not compiled with libxc
344 * they are before $PYTEST_START_AFTER
345 """
346 from gpaw import get_libraries
347 libraries = get_libraries()
349 if world.size > 1:
350 for mark in item.iter_markers():
351 if mark.name == 'serial':
352 pytest.skip('Only run in serial')
353 else:
354 for mark in item.iter_markers():
355 if mark.name == 'parallel':
356 pytest.skip('Only run in parallel')
358 if item.location[0] <= os.environ.get('PYTEST_START_AFTER', ''):
359 pytest.skip('Not after $PYTEST_START_AFTER')
360 return
362 if libraries['libxc']:
363 return
365 if any(mark.name in {'libxc', 'mgga'}
366 for mark in item.iter_markers()):
367 pytest.skip('No LibXC.')
370@pytest.fixture
371def scalapack():
372 """Skip if not compiled with sl.
374 This fixture otherwise does not return or do anything."""
375 from gpaw.utilities import compiled_with_sl
376 if not compiled_with_sl():
377 pytest.skip('no scalapack')
380@pytest.fixture
381def needs_ase_master():
382 from ase.utils.filecache import MultiFileJSONCache
383 try:
384 MultiFileJSONCache('bla-bla', comm=None)
385 except TypeError:
386 pytest.skip('ASE is too old')
389def pytest_report_header(config, start_path):
390 # Use this to add custom information to the pytest printout.
391 yield f'GPAW MPI rank={world.rank}, size={world.size}'
393 # We want the user to be able to see where gpw files are cached,
394 # but the only way to see the cache location is to make a directory
395 # inside it. mkdir('') returns the toplevel cache dir without
396 # actually creating a subdirectory:
397 cachedir = config.cache.mkdir('')
398 yield f'Cache directory including gpw files: {cachedir}'
401@pytest.fixture
402def rng():
403 """Seeded random number generator.
405 Tests should be deterministic and should use this
406 fixture or initialize their own rng."""
407 return np.random.default_rng(42)
410@pytest.fixture
411def gpaw_new() -> bool:
412 """Are we testing the new code?"""
413 return GPAW_NEW