Coverage for /builds/kinetik161/ase/ase/calculators/onetep.py: 32.65%

49 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-12-10 11:04 +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 pathlib import Path 

8 

9from ase.calculators.genericfileio import (CalculatorTemplate, 

10 GenericFileIOCalculator, 

11 read_stdout, 

12 BaseProfile) 

13from ase.io import read, write 

14 

15# TARP: New setup exc should be the onetep binary itself 

16# def find_onetep_command(cmd): 

17# exploded_cmd = cmd.split() 

18# n_cmd = len(exploded_cmd) 

19# if n_cmd == 1: 

20# return cmd 

21# for substring in exploded_cmd: 

22# if 'onetep' in substring: 

23# return substring 

24# return None 

25 

26 

27class OnetepProfile(BaseProfile): 

28 def __init__(self, binary, **kwargs): 

29 super().__init__(**kwargs) 

30 self.binary = binary 

31 

32 def version(self): 

33 # onetep_exec = find_onetep_command(self.argv) 

34 lines = read_stdout(self.binary) 

35 return self.parse_version(lines) 

36 

37 def parse_version(lines): 

38 return '1.0.0' 

39 

40 def get_calculator_command(self, inputfile): 

41 return [self.binary, str(inputfile)] 

42 

43 

44class OnetepTemplate(CalculatorTemplate): 

45 def __init__(self, label, append): 

46 super().__init__( 

47 name='ONETEP', 

48 implemented_properties=[ 

49 'energy', 

50 'free_energy', 

51 'forces', 

52 'stress']) 

53 self.label = label 

54 self.input = label + '.dat' 

55 self.output = label + '.out' 

56 self.error = label + '.err' 

57 self.append = append 

58 

59 def execute(self, directory, profile): 

60 profile.run(directory, self.input, self.output, self.error, 

61 self.append) 

62 

63 def read_results(self, directory): 

64 output_path = directory / self.output 

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

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

67 

68 def write_input(self, directory, atoms, parameters, properties): 

69 input_path = directory / self.input 

70 write(input_path, atoms, format='onetep-in', 

71 properties=properties, **parameters) 

72 

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

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

75 

76 

77class Onetep(GenericFileIOCalculator): 

78 """ 

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

80 Need the env variable "ASE_ONETEP_COMMAND" defined to 

81 properly work. All other options are passed in kwargs. 

82 

83 Parameters 

84 ---------- 

85 autorestart : Bool 

86 When activated, manages restart keywords automatically. 

87 append: Bool 

88 Append to output instead of overwriting. 

89 directory: str 

90 Directory where to run the calculation(s). 

91 keywords: dict 

92 Dictionary with ONETEP keywords to write, 

93 keywords with lists as values will be 

94 treated like blocks, with each element 

95 of list being a different line. 

96 label: str 

97 Name used for the ONETEP prefix. 

98 xc: str 

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

100 ngwfs_count: int|list|dict 

101 Behaviour depends on the type: 

102 int: every species will have this amount 

103 of ngwfs. 

104 list: list of int, will be attributed 

105 alphabetically to species: 

106 dict: keys are species name(s), 

107 value are their number: 

108 ngwfs_radius: int|list|dict 

109 Behaviour depends on the type: 

110 float: every species will have this radius. 

111 list: list of float, will be attributed 

112 alphabetically to species: 

113 [10.0, 9.0] 

114 dict: keys are species name(s), 

115 value are their radius: 

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

117 pseudopotentials: list|dict 

118 Behaviour depends on the type: 

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

120 alphabetically to specie(s): 

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

122 dict: keys are species name(s) their 

123 value are the pseudopotential file to use: 

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

125 pseudo_path: str 

126 Where to look for pseudopotential, correspond 

127 to the pseudo_path keyword of ONETEP. 

128 

129 .. note:: 

130 write_forces is always turned on by default 

131 when using this interface. 

132 

133 .. note:: 

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

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

136 are valid ONETEP keywords. 

137 """ 

138 # TARP: I thought GenericFileIO calculators no longer had atoms attached 

139 # to them 

140 def __init__( 

141 self, 

142 label='onetep', 

143 directory='.', 

144 profile=None, 

145 append=False, 

146 autorestart=True, 

147 atoms=None, 

148 parallel_info=None, 

149 parallel=True, 

150 **kwargs): 

151 

152 self.directory = Path(directory) 

153 self.autorestart = autorestart 

154 self.label = label 

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

156 self.append = append 

157 self.template = OnetepTemplate(label, append=self.append) 

158 

159 kwargs['autorestart'] = self.autorestart 

160 kwargs['directory'] = self.directory 

161 kwargs['label'] = self.label 

162 super().__init__(profile=profile, template=self.template, 

163 directory=directory, 

164 parameters=kwargs, 

165 parallel=parallel, 

166 parallel_info=parallel_info) 

167 # Copy is probably not needed, but just in case 

168 if atoms is not None: 

169 self.atoms = atoms.copy() 

170 if atoms.calc is not None: 

171 # TARP: Does this make sense? Why append the previous 

172 # calculators results to a new calculator. Especially 

173 # without checking if the parameters are the same as well. 

174 self.results = atoms.calc.results.copy()