Coverage for /builds/ase/ase/ase/calculators/elk.py: 89.09%
55 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1"""
2`Elk <https://elk.sourceforge.io>`_ is an all-electron full-potential linearised
3augmented-plane wave (LAPW) code.
5.. versionchanged:: 3.26.0
6 :class:`ELK` is now a subclass of :class:`GenericFileIOCalculator`.
8.. |config| replace:: ``config.ini``
9.. _config: calculators.html#calculator-configuration
11:class:`ELK` can be configured with |config|_.
13.. code-block:: ini
15 [elk]
16 command = /path/to/elk
17 sppath = /path/to/species
19If you need to override it for programmatic control of the ``elk`` command,
20use :class:`ElkProfile`.
22.. code-block:: python
24 from ase.calculators.elk import ELK, ElkProfile
26 profile = ElkProfile(command='/path/to/elk')
27 calc = ELK(profile=profile)
29"""
31import os
32import re
33import warnings
34from pathlib import Path
35from typing import Optional
37from ase.calculators.genericfileio import (
38 BaseProfile,
39 CalculatorTemplate,
40 GenericFileIOCalculator,
41 read_stdout,
42)
43from ase.io.elk import ElkReader, write_elk_in
45COMPATIBILITY_MSG = (
46 '`ELK` has been restructured. '
47 'Please use `ELK(profile=ElkProfile(command))` instead.'
48)
51class ElkProfile(BaseProfile):
52 """Profile for :class:`ELK`."""
54 configvars = {'sppath'}
56 def __init__(self, command, sppath: Optional[str] = None, **kwargs) -> None:
57 super().__init__(command, **kwargs)
58 self.sppath = sppath
60 def get_calculator_command(self, inputfile):
61 return []
63 def version(self):
64 output = read_stdout(self._split_command)
65 match = re.search(r'Elk code version (\S+)', output, re.M)
66 return match.group(1)
69class ElkTemplate(CalculatorTemplate):
70 """Template for :class:`ELK`."""
72 def __init__(self):
73 super().__init__('elk', ['energy', 'forces'])
74 self.inputname = 'elk.in'
75 self.outputname = 'elk.out'
77 def write_input(
78 self,
79 profile: ElkProfile,
80 directory,
81 atoms,
82 parameters,
83 properties,
84 ):
85 directory = Path(directory)
86 parameters = dict(parameters)
87 if 'forces' in properties:
88 parameters['tforce'] = True
89 if 'sppath' not in parameters and profile.sppath:
90 parameters['sppath'] = profile.sppath
91 write_elk_in(directory / self.inputname, atoms, parameters=parameters)
93 def execute(self, directory, profile: ElkProfile) -> None:
94 profile.run(directory, self.inputname, self.outputname)
96 def read_results(self, directory):
97 from ase.outputs import Properties
99 reader = ElkReader(directory)
100 dct = dict(reader.read_everything())
102 converged = dct.pop('converged')
103 if not converged:
104 raise RuntimeError('Did not converge')
106 # (Filter results thorugh Properties for error detection)
107 props = Properties(dct)
108 return dict(props)
110 def load_profile(self, cfg, **kwargs):
111 return ElkProfile.from_config(cfg, self.name, **kwargs)
114class ELK(GenericFileIOCalculator):
115 """Elk calculator."""
117 def __init__(
118 self,
119 *,
120 profile=None,
121 command=GenericFileIOCalculator._deprecated,
122 label=GenericFileIOCalculator._deprecated,
123 directory='.',
124 **kwargs,
125 ) -> None:
126 """
128 Parameters
129 ----------
130 **kwargs : dict, optional
131 ASE standard keywords like ``xc``, ``kpts`` and ``smearing`` or any
132 Elk-native keywords.
134 Examples
135 --------
136 >>> calc = ELK(tasks=0, ngridk=(3, 3, 3))
138 """
139 if command is not self._deprecated:
140 raise RuntimeError(COMPATIBILITY_MSG)
142 if label is not self._deprecated:
143 msg = 'Ignoring label, please use directory instead'
144 warnings.warn(msg, FutureWarning)
146 if 'ASE_ELK_COMMAND' in os.environ and profile is None:
147 warnings.warn(COMPATIBILITY_MSG, FutureWarning)
149 super().__init__(
150 template=ElkTemplate(),
151 profile=profile,
152 directory=directory,
153 parameters=kwargs,
154 )