Coverage for ase / calculators / elk.py: 88.89%

54 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-30 08:22 +0000

1""" 

2`Elk <https://elk.sourceforge.io>`_ is an all-electron full-potential linearised 

3augmented-plane wave (LAPW) code. 

4 

5.. versionchanged:: 3.26.0 

6 :class:`ELK` is now a subclass of :class:`GenericFileIOCalculator`. 

7 

8.. |config| replace:: ``config.ini`` 

9.. _config: calculators.html#calculator-configuration 

10 

11:class:`ELK` can be configured with |config|_. 

12 

13.. code-block:: ini 

14 

15 [elk] 

16 command = /path/to/elk 

17 sppath = /path/to/species 

18 

19If you need to override it for programmatic control of the ``elk`` command, 

20use :class:`ElkProfile`. 

21 

22.. code-block:: python 

23 

24 from ase.calculators.elk import ELK, ElkProfile 

25 

26 profile = ElkProfile(command='/path/to/elk') 

27 calc = ELK(profile=profile) 

28 

29""" 

30 

31import os 

32import re 

33import warnings 

34from pathlib import Path 

35 

36from ase.calculators.genericfileio import ( 

37 BaseProfile, 

38 CalculatorTemplate, 

39 GenericFileIOCalculator, 

40 read_stdout, 

41) 

42from ase.io.elk import ElkReader, write_elk_in 

43 

44COMPATIBILITY_MSG = ( 

45 '`ELK` has been restructured. ' 

46 'Please use `ELK(profile=ElkProfile(command))` instead.' 

47) 

48 

49 

50class ElkProfile(BaseProfile): 

51 """Profile for :class:`ELK`.""" 

52 

53 configvars = {'sppath'} 

54 

55 def __init__(self, command, sppath: str | None = None, **kwargs) -> None: 

56 super().__init__(command, **kwargs) 

57 self.sppath = sppath 

58 

59 def get_calculator_command(self, inputfile): 

60 return [] 

61 

62 def version(self): 

63 output = read_stdout(self._split_command) 

64 match = re.search(r'Elk code version (\S+)', output, re.M) 

65 return match.group(1) 

66 

67 

68class ElkTemplate(CalculatorTemplate): 

69 """Template for :class:`ELK`.""" 

70 

71 def __init__(self): 

72 super().__init__('elk', ['energy', 'forces']) 

73 self.inputname = 'elk.in' 

74 self.outputname = 'elk.out' 

75 

76 def write_input( 

77 self, 

78 profile: ElkProfile, 

79 directory, 

80 atoms, 

81 parameters, 

82 properties, 

83 ): 

84 directory = Path(directory) 

85 parameters = dict(parameters) 

86 if 'forces' in properties: 

87 parameters['tforce'] = True 

88 if 'sppath' not in parameters and profile.sppath: 

89 parameters['sppath'] = profile.sppath 

90 write_elk_in(directory / self.inputname, atoms, parameters=parameters) 

91 

92 def execute(self, directory, profile: ElkProfile) -> None: 

93 profile.run(directory, self.inputname, self.outputname) 

94 

95 def read_results(self, directory): 

96 from ase.outputs import Properties 

97 

98 reader = ElkReader(directory) 

99 dct = dict(reader.read_everything()) 

100 

101 converged = dct.pop('converged') 

102 if not converged: 

103 raise RuntimeError('Did not converge') 

104 

105 # (Filter results thorugh Properties for error detection) 

106 props = Properties(dct) 

107 return dict(props) 

108 

109 def load_profile(self, cfg, **kwargs): 

110 return ElkProfile.from_config(cfg, self.name, **kwargs) 

111 

112 

113class ELK(GenericFileIOCalculator): 

114 """Elk calculator.""" 

115 

116 def __init__( 

117 self, 

118 *, 

119 profile=None, 

120 command=GenericFileIOCalculator._deprecated, 

121 label=GenericFileIOCalculator._deprecated, 

122 directory='.', 

123 **kwargs, 

124 ) -> None: 

125 """ 

126 

127 Parameters 

128 ---------- 

129 **kwargs : dict, optional 

130 ASE standard keywords like ``xc``, ``kpts`` and ``smearing`` (in ASE 

131 units) or any Elk-native keywords (numeric parameters in Elk units). 

132 All numeric parameters that are passed using Elk-native keys must be 

133 in Elk units. 

134 

135 Examples 

136 -------- 

137 >>> import numpy as np 

138 >>> from ase.calculators.elk import ELK 

139 >>> calc = ELK(tasks=[0], ngridk=np.array([3, 3, 3])) 

140 >>> params = {'tasks': [[0], [10]], 'ngridk': [8, 8, 8], 'nempty': 8, 

141 'bfieldc': np.array((0.0, 0.0, -0.01)), 'spinpol': True, 

142 'dft+u': ((2, 1), (1, 2, 0.183, 0.034911967))} 

143 >>> calc = ELK(**params) 

144 Note: ``np.array((0.0, 0.0, -0.01))``, ``[0.0, 0.0, -0.01]``, 

145 ``[[0.0, 0.0, -0.01]]``, and any combinations of lists, tuples, 

146 and ndarrays, are equivalent 

147 """ 

148 if command is not self._deprecated: 

149 raise RuntimeError(COMPATIBILITY_MSG) 

150 

151 if label is not self._deprecated: 

152 msg = 'Ignoring label, please use directory instead' 

153 warnings.warn(msg, FutureWarning) 

154 

155 if 'ASE_ELK_COMMAND' in os.environ and profile is None: 

156 warnings.warn(COMPATIBILITY_MSG, FutureWarning) 

157 

158 super().__init__( 

159 template=ElkTemplate(), 

160 profile=profile, 

161 directory=directory, 

162 parameters=kwargs, 

163 )