Coverage for /builds/ase/ase/ase/calculators/kim/calculators.py: 23.81%

84 statements  

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

1# fmt: off 

2 

3import os 

4import re 

5 

6from ase.calculators.lammps import convert 

7from ase.calculators.lammpslib import LAMMPSlib 

8from ase.calculators.lammpsrun import LAMMPS 

9from ase.data import atomic_masses, atomic_numbers 

10 

11from .exceptions import KIMCalculatorError 

12from .kimmodel import KIMModelCalculator 

13 

14 

15def KIMCalculator(model_name, options, debug): 

16 """ 

17 Used only for Portable Models 

18 """ 

19 

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

21 

22 _check_conflict_options(options, options_not_allowed, 

23 simulator="kimmodel") 

24 

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

26 

27 

28def LAMMPSRunCalculator( 

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

30): 

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

32 specifically requested""" 

33 

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

35 """ 

36 Extract parameters for LAMMPS calculator from model definition lines. 

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

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

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

40 """ 

41 parameters = {} 

42 

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

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

45 # to know the proper format 

46 if atom_style: 

47 parameters["atom_style"] = atom_style 

48 

49 # Set units to prevent them from defaulting to metal 

50 parameters["units"] = supported_units 

51 

52 parameters["model_init"] = [ 

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

54 ] 

55 

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

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

58 ) 

59 

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

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

62 parameters["masses"] = [] 

63 for i, species in enumerate(supported_species): 

64 if species not in atomic_numbers: 

65 raise KIMCalculatorError( 

66 "Could not determine mass of unknown species " 

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

68 ) 

69 massstr = str( 

70 convert( 

71 atomic_masses[atomic_numbers[species]], 

72 "mass", 

73 "ASE", 

74 supported_units, 

75 ) 

76 ) 

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

78 

79 return parameters 

80 

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

82 "keep_tmp_files"] 

83 

84 _check_conflict_options(options, options_not_allowed, 

85 simulator="lammpsrun") 

86 

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

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

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

90 

91 # Simulator Models will supply their own units from their 

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

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

94 

95 # Set up kim_init and kim_interactions lines 

96 parameters = get_params( 

97 model_name, 

98 supported_units, 

99 supported_species, 

100 atom_style) 

101 

102 return LAMMPS( 

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

104 **options 

105 ) 

106 

107 

108def LAMMPSLibCalculator(model_name, supported_species, 

109 supported_units, options): 

110 """ 

111 Only used for LAMMPS Simulator Models 

112 """ 

113 options_not_allowed = [ 

114 "lammps_header", 

115 "lmpcmds", 

116 "atom_types", 

117 "log_file", 

118 "keep_alive", 

119 ] 

120 

121 _check_conflict_options(options, options_not_allowed, 

122 simulator="lammpslib") 

123 # Set up LAMMPS header commands lookup table 

124 

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

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

127 # internally 

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

129 

130 model_init.append( 

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

132 ) 

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

134 

135 # Assign atom types to species 

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

137 kim_interactions = [ 

138 "kim_interactions {}".format( 

139 (" ").join(supported_species))] 

140 

141 # Return LAMMPSlib calculator 

142 return LAMMPSlib( 

143 lammps_header=model_init, 

144 lammps_name=None, 

145 lmpcmds=kim_interactions, 

146 post_changebox_cmds=kim_interactions, 

147 atom_types=atom_types, 

148 log_file="lammps.log", 

149 keep_alive=True, 

150 **options 

151 ) 

152 

153 

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

155 """ 

156 Can be used with either Portable Models or Simulator Models 

157 """ 

158 import asap3 

159 

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

161 

162 _check_conflict_options( 

163 options, 

164 options_not_allowed[model_type], 

165 simulator="asap") 

166 

167 if model_type == "pm": 

168 

169 return asap3.OpenKIMcalculator( 

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

171 ) 

172 

173 elif model_type == "sm": 

174 model_defn = kwargs["model_defn"] 

175 supported_units = kwargs["supported_units"] 

176 

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

178 if supported_units != "ase": 

179 raise KIMCalculatorError( 

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

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

182 ) 

183 

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

185 # that is a non-empty string 

186 if len(model_defn) == 0: 

187 raise KIMCalculatorError( 

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

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

190 ) 

191 elif len(model_defn) > 1: 

192 raise KIMCalculatorError( 

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

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

195 ) 

196 

197 if "" in model_defn: 

198 raise KIMCalculatorError( 

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

200 "file of Simulator " 

201 "Model {}".format(model_name) 

202 ) 

203 

204 model_defn = model_defn[0].strip() 

205 

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

207 # (1) EMT 

208 # (2) EMT(EMTRasmussenParameters) 

209 # (3) EMT(EMTMetalGlassParameters) 

210 model_defn_is_valid = False 

211 if model_defn.startswith("EMT"): 

212 # Pull out potential parameters 

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

214 if mobj is None: 

215 asap_calc = asap3.EMT() 

216 else: 

217 pp = mobj.group(1) 

218 

219 # Currently we only supported two specific EMT models 

220 # that are built into ASAP 

221 if pp.startswith("EMTRasmussenParameters"): 

222 asap_calc = asap3.EMT( 

223 parameters=asap3.EMTRasmussenParameters()) 

224 model_defn_is_valid = True 

225 elif pp.startswith("EMTMetalGlassParameters"): 

226 asap_calc = asap3.EMT( 

227 parameters=asap3.EMTMetalGlassParameters()) 

228 model_defn_is_valid = True 

229 

230 if not model_defn_is_valid: 

231 raise KIMCalculatorError( 

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

233 model_defn) 

234 ) 

235 

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

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

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

239 asap_calc.set_subtractE0(False) 

240 

241 return asap_calc 

242 

243 

244def _check_conflict_options(options, options_not_allowed, simulator): 

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

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

247 set internally in this package.""" 

248 s1 = set(options) 

249 s2 = set(options_not_allowed) 

250 common = s1.intersection(s2) 

251 

252 if common: 

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

254 

255 msg = ( 

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

257 '{} provided in "options", ' 

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

259 "calculator".format(simulator, options_in_not_allowed) 

260 ) 

261 

262 raise KIMCalculatorError(msg)