Coverage for /builds/ase/ase/ase/calculators/onetep.py: 41.86%

43 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1"""ONETEP interface for the Atomic Simulation Environment (ASE) package 

2 

3T. Demeyere, T.Demeyere@soton.ac.uk (2023) 

4 

5https://onetep.org""" 

6 

7from copy import deepcopy 

8 

9from ase.calculators.genericfileio import ( 

10 BaseProfile, 

11 CalculatorTemplate, 

12 GenericFileIOCalculator, 

13 read_stdout, 

14) 

15from ase.io import read, write 

16 

17 

18class OnetepProfile(BaseProfile): 

19 """ 

20 ONETEP profile class. 

21 """ 

22 

23 configvars = {'pseudo_path'} 

24 

25 def __init__(self, command, pseudo_path, **kwargs): 

26 """ 

27 Parameters 

28 ---------- 

29 command: str 

30 The onetep command (not including inputfile). 

31 **kwargs: dict 

32 Additional kwargs are passed to the BaseProfile class. 

33 """ 

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

35 self.pseudo_path = pseudo_path 

36 

37 def version(self): 

38 lines = read_stdout(self._split_command) 

39 return self.parse_version(lines) 

40 

41 def parse_version(lines): 

42 return '1.0.0' 

43 

44 def get_calculator_command(self, inputfile): 

45 return [str(inputfile)] 

46 

47 

48class OnetepTemplate(CalculatorTemplate): 

49 _label = 'onetep' 

50 

51 def __init__(self, append): 

52 super().__init__( 

53 'ONETEP', 

54 implemented_properties=[ 

55 'energy', 

56 'free_energy', 

57 'forces', 

58 'stress', 

59 ], 

60 ) 

61 self.inputname = f'{self._label}.dat' 

62 self.outputname = f'{self._label}.out' 

63 self.errorname = f'{self._label}.err' 

64 self.append = append 

65 

66 def execute(self, directory, profile): 

67 profile.run( 

68 directory, 

69 self.inputname, 

70 self.outputname, 

71 self.errorname, 

72 append=self.append, 

73 ) 

74 

75 def read_results(self, directory): 

76 output_path = directory / self.outputname 

77 atoms = read(output_path, format='onetep-out') 

78 return dict(atoms.calc.properties()) 

79 

80 def write_input(self, profile, directory, atoms, parameters, properties): 

81 input_path = directory / self.inputname 

82 

83 parameters = deepcopy(parameters) 

84 

85 keywords = parameters.get('keywords', {}) 

86 keywords.setdefault('pseudo_path', profile.pseudo_path) 

87 parameters['keywords'] = keywords 

88 

89 write( 

90 input_path, 

91 atoms, 

92 format='onetep-in', 

93 properties=properties, 

94 **parameters, 

95 ) 

96 

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

98 return OnetepProfile.from_config(cfg, self.name, **kwargs) 

99 

100 

101class Onetep(GenericFileIOCalculator): 

102 """ 

103 Class for the ONETEP calculator, uses ase/io/onetep.py. 

104 

105 Parameters 

106 ---------- 

107 autorestart : Bool 

108 When activated, manages restart keywords automatically. 

109 append: Bool 

110 Append to output instead of overwriting. 

111 directory: str 

112 Directory where to run the calculation(s). 

113 keywords: dict 

114 Dictionary with ONETEP keywords to write, 

115 keywords with lists as values will be 

116 treated like blocks, with each element 

117 of list being a different line. 

118 xc: str 

119 DFT xc to use e.g (PBE, RPBE, ...). 

120 ngwfs_count: int|list|dict 

121 Behaviour depends on the type: 

122 int: every species will have this amount 

123 of ngwfs. 

124 list: list of int, will be attributed 

125 alphabetically to species: 

126 dict: keys are species name(s), 

127 value are their number: 

128 ngwfs_radius: int|list|dict 

129 Behaviour depends on the type: 

130 float: every species will have this radius. 

131 list: list of float, will be attributed 

132 alphabetically to species: 

133 [10.0, 9.0] 

134 dict: keys are species name(s), 

135 value are their radius: 

136 {'Na': 9.0, 'Cl': 10.0} 

137 pseudopotentials: list|dict 

138 Behaviour depends on the type: 

139 list: list of string(s), will be attributed 

140 alphabetically to specie(s): 

141 ['Cl.usp', 'Na.usp'] 

142 dict: keys are species name(s) their 

143 value are the pseudopotential file to use: 

144 {'Na': 'Na.usp', 'Cl': 'Cl.usp'} 

145 pseudo_path: str 

146 Where to look for pseudopotential, correspond 

147 to the pseudo_path keyword of ONETEP. 

148 

149 .. note:: 

150 write_forces is always turned on by default 

151 when using this interface. 

152 

153 .. note:: 

154 Little to no check is performed on the keywords provided by the user 

155 via the keyword dictionary, it is the user responsibility that they 

156 are valid ONETEP keywords. 

157 """ 

158 

159 def __init__(self, *, profile=None, directory='.', **kwargs): 

160 self.keywords = kwargs.get('keywords', None) 

161 self.template = OnetepTemplate(append=kwargs.pop('append', False)) 

162 

163 super().__init__( 

164 profile=profile, 

165 template=self.template, 

166 directory=directory, 

167 parameters=kwargs, 

168 )