Coverage for /builds/debichem-team/python-ase/ase/calculators/kim/calculators.py: 23.81%

84 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-03-06 04:00 +0000

1import os 

2import re 

3 

4from ase.calculators.lammps import convert 

5from ase.calculators.lammpslib import LAMMPSlib 

6from ase.calculators.lammpsrun import LAMMPS 

7from ase.data import atomic_masses, atomic_numbers 

8 

9from .exceptions import KIMCalculatorError 

10from .kimmodel import KIMModelCalculator 

11 

12 

13def KIMCalculator(model_name, options, debug): 

14 """ 

15 Used only for Portable Models 

16 """ 

17 

18 options_not_allowed = ["modelname", "debug"] 

19 

20 _check_conflict_options(options, options_not_allowed, 

21 simulator="kimmodel") 

22 

23 return KIMModelCalculator(model_name, debug=debug, **options) 

24 

25 

26def LAMMPSRunCalculator( 

27 model_name, model_type, supported_species, options, debug, **kwargs 

28): 

29 """Used for Portable Models or LAMMPS Simulator Models if 

30 specifically requested""" 

31 

32 def get_params(model_name, supported_units, supported_species, atom_style): 

33 """ 

34 Extract parameters for LAMMPS calculator from model definition lines. 

35 Returns a dictionary with entries for "pair_style" and "pair_coeff". 

36 Expects there to be only one "pair_style" line. There can be multiple 

37 "pair_coeff" lines (result is returned as a list). 

38 """ 

39 parameters = {} 

40 

41 # In case the SM supplied its own atom_style in its model-init 

42 # -- only needed because lammpsrun writes data files and needs 

43 # to know the proper format 

44 if atom_style: 

45 parameters["atom_style"] = atom_style 

46 

47 # Set units to prevent them from defaulting to metal 

48 parameters["units"] = supported_units 

49 

50 parameters["model_init"] = [ 

51 f"kim_init {model_name} {supported_units}{os.linesep}" 

52 ] 

53 

54 parameters["kim_interactions"] = "kim_interactions {}{}".format( 

55 (" ").join(supported_species), os.linesep 

56 ) 

57 

58 # For every species in "supported_species", add an entry to the 

59 # "masses" key in dictionary "parameters". 

60 parameters["masses"] = [] 

61 for i, species in enumerate(supported_species): 

62 if species not in atomic_numbers: 

63 raise KIMCalculatorError( 

64 "Could not determine mass of unknown species " 

65 "{} listed as supported by model".format(species) 

66 ) 

67 massstr = str( 

68 convert( 

69 atomic_masses[atomic_numbers[species]], 

70 "mass", 

71 "ASE", 

72 supported_units, 

73 ) 

74 ) 

75 parameters["masses"].append(str(i + 1) + " " + massstr) 

76 

77 return parameters 

78 

79 options_not_allowed = ["parameters", "files", "specorder", 

80 "keep_tmp_files"] 

81 

82 _check_conflict_options(options, options_not_allowed, 

83 simulator="lammpsrun") 

84 

85 # If no atom_style kwarg is passed, lammpsrun will default to 

86 # atom_style atomic, which is what we want for KIM Portable Models 

87 atom_style = kwargs.get("atom_style", None) 

88 

89 # Simulator Models will supply their own units from their 

90 # metadata. For Portable Models, we use "metal" units. 

91 supported_units = kwargs.get("supported_units", "metal") 

92 

93 # Set up kim_init and kim_interactions lines 

94 parameters = get_params( 

95 model_name, 

96 supported_units, 

97 supported_species, 

98 atom_style) 

99 

100 return LAMMPS( 

101 **parameters, specorder=supported_species, keep_tmp_files=debug, 

102 **options 

103 ) 

104 

105 

106def LAMMPSLibCalculator(model_name, supported_species, 

107 supported_units, options): 

108 """ 

109 Only used for LAMMPS Simulator Models 

110 """ 

111 options_not_allowed = [ 

112 "lammps_header", 

113 "lmpcmds", 

114 "atom_types", 

115 "log_file", 

116 "keep_alive", 

117 ] 

118 

119 _check_conflict_options(options, options_not_allowed, 

120 simulator="lammpslib") 

121 # Set up LAMMPS header commands lookup table 

122 

123 # This units command actually has no effect, but is necessary because 

124 # LAMMPSlib looks in the header lines for units in order to set them 

125 # internally 

126 model_init = ["units " + supported_units + os.linesep] 

127 

128 model_init.append( 

129 f"kim_init {model_name} {supported_units}{os.linesep}" 

130 ) 

131 model_init.append("atom_modify map array sort 0 0" + os.linesep) 

132 

133 # Assign atom types to species 

134 atom_types = {s: i_s + 1 for i_s, s in enumerate(supported_species)} 

135 kim_interactions = [ 

136 "kim_interactions {}".format( 

137 (" ").join(supported_species))] 

138 

139 # Return LAMMPSlib calculator 

140 return LAMMPSlib( 

141 lammps_header=model_init, 

142 lammps_name=None, 

143 lmpcmds=kim_interactions, 

144 post_changebox_cmds=kim_interactions, 

145 atom_types=atom_types, 

146 log_file="lammps.log", 

147 keep_alive=True, 

148 **options 

149 ) 

150 

151 

152def ASAPCalculator(model_name, model_type, options, **kwargs): 

153 """ 

154 Can be used with either Portable Models or Simulator Models 

155 """ 

156 import asap3 

157 

158 options_not_allowed = {"pm": ["name", "verbose"], "sm": ["Params"]} 

159 

160 _check_conflict_options( 

161 options, 

162 options_not_allowed[model_type], 

163 simulator="asap") 

164 

165 if model_type == "pm": 

166 

167 return asap3.OpenKIMcalculator( 

168 name=model_name, verbose=kwargs["verbose"], **options 

169 ) 

170 

171 elif model_type == "sm": 

172 model_defn = kwargs["model_defn"] 

173 supported_units = kwargs["supported_units"] 

174 

175 # Verify units (ASAP models are expected to work with "ase" units) 

176 if supported_units != "ase": 

177 raise KIMCalculatorError( 

178 'KIM Simulator Model units are "{}", but expected to ' 

179 'be "ase" for ASAP.'.format(supported_units) 

180 ) 

181 

182 # Check model_defn to make sure there's only one element in it 

183 # that is a non-empty string 

184 if len(model_defn) == 0: 

185 raise KIMCalculatorError( 

186 "model-defn is an empty list in metadata file of " 

187 "Simulator Model {}".format(model_name) 

188 ) 

189 elif len(model_defn) > 1: 

190 raise KIMCalculatorError( 

191 "model-defn should contain only one entry for an ASAP " 

192 "model (found {} lines)".format(len(model_defn)) 

193 ) 

194 

195 if "" in model_defn: 

196 raise KIMCalculatorError( 

197 "model-defn contains an empty string in metadata " 

198 "file of Simulator " 

199 "Model {}".format(model_name) 

200 ) 

201 

202 model_defn = model_defn[0].strip() 

203 

204 # Instantiate calculator from ASAP. Currently, this must be one of: 

205 # (1) EMT 

206 # (2) EMT(EMTRasmussenParameters) 

207 # (3) EMT(EMTMetalGlassParameters) 

208 model_defn_is_valid = False 

209 if model_defn.startswith("EMT"): 

210 # Pull out potential parameters 

211 mobj = re.search(r"\(([A-Za-z0-9_\(\)]+)\)", model_defn) 

212 if mobj is None: 

213 asap_calc = asap3.EMT() 

214 else: 

215 pp = mobj.group(1) 

216 

217 # Currently we only supported two specific EMT models 

218 # that are built into ASAP 

219 if pp.startswith("EMTRasmussenParameters"): 

220 asap_calc = asap3.EMT( 

221 parameters=asap3.EMTRasmussenParameters()) 

222 model_defn_is_valid = True 

223 elif pp.startswith("EMTMetalGlassParameters"): 

224 asap_calc = asap3.EMT( 

225 parameters=asap3.EMTMetalGlassParameters()) 

226 model_defn_is_valid = True 

227 

228 if not model_defn_is_valid: 

229 raise KIMCalculatorError( 

230 'Unknown model "{}" requested for simulator asap.'.format( 

231 model_defn) 

232 ) 

233 

234 # Disable undocumented feature for the EMT self.calculators to 

235 # take the energy of an isolated atoms as zero. (Otherwise it 

236 # is taken to be that of perfect FCC.) 

237 asap_calc.set_subtractE0(False) 

238 

239 return asap_calc 

240 

241 

242def _check_conflict_options(options, options_not_allowed, simulator): 

243 """Check whether options intended to be passed to a given calculator 

244 are allowed. Some options are not allowed because they must be 

245 set internally in this package.""" 

246 s1 = set(options) 

247 s2 = set(options_not_allowed) 

248 common = s1.intersection(s2) 

249 

250 if common: 

251 options_in_not_allowed = ", ".join([f'"{s}"' for s in common]) 

252 

253 msg = ( 

254 'Simulator "{}" does not support argument(s): ' 

255 '{} provided in "options", ' 

256 "because it is (they are) determined internally within the KIM " 

257 "calculator".format(simulator, options_in_not_allowed) 

258 ) 

259 

260 raise KIMCalculatorError(msg)