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

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 

35from typing import Optional 

36 

37from ase.calculators.genericfileio import ( 

38 BaseProfile, 

39 CalculatorTemplate, 

40 GenericFileIOCalculator, 

41 read_stdout, 

42) 

43from ase.io.elk import ElkReader, write_elk_in 

44 

45COMPATIBILITY_MSG = ( 

46 '`ELK` has been restructured. ' 

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

48) 

49 

50 

51class ElkProfile(BaseProfile): 

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

53 

54 configvars = {'sppath'} 

55 

56 def __init__(self, command, sppath: Optional[str] = None, **kwargs) -> None: 

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

58 self.sppath = sppath 

59 

60 def get_calculator_command(self, inputfile): 

61 return [] 

62 

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) 

67 

68 

69class ElkTemplate(CalculatorTemplate): 

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

71 

72 def __init__(self): 

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

74 self.inputname = 'elk.in' 

75 self.outputname = 'elk.out' 

76 

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) 

92 

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

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

95 

96 def read_results(self, directory): 

97 from ase.outputs import Properties 

98 

99 reader = ElkReader(directory) 

100 dct = dict(reader.read_everything()) 

101 

102 converged = dct.pop('converged') 

103 if not converged: 

104 raise RuntimeError('Did not converge') 

105 

106 # (Filter results thorugh Properties for error detection) 

107 props = Properties(dct) 

108 return dict(props) 

109 

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

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

112 

113 

114class ELK(GenericFileIOCalculator): 

115 """Elk calculator.""" 

116 

117 def __init__( 

118 self, 

119 *, 

120 profile=None, 

121 command=GenericFileIOCalculator._deprecated, 

122 label=GenericFileIOCalculator._deprecated, 

123 directory='.', 

124 **kwargs, 

125 ) -> None: 

126 """ 

127 

128 Parameters 

129 ---------- 

130 **kwargs : dict, optional 

131 ASE standard keywords like ``xc``, ``kpts`` and ``smearing`` or any 

132 Elk-native keywords. 

133 

134 Examples 

135 -------- 

136 >>> calc = ELK(tasks=0, ngridk=(3, 3, 3)) 

137 

138 """ 

139 if command is not self._deprecated: 

140 raise RuntimeError(COMPATIBILITY_MSG) 

141 

142 if label is not self._deprecated: 

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

144 warnings.warn(msg, FutureWarning) 

145 

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

147 warnings.warn(COMPATIBILITY_MSG, FutureWarning) 

148 

149 super().__init__( 

150 template=ElkTemplate(), 

151 profile=profile, 

152 directory=directory, 

153 parameters=kwargs, 

154 )