Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Wrappers that provide a minimal interface to kimpy methods and objects 

3 

4Daniel S. Karls 

5University of Minnesota 

6""" 

7 

8import functools 

9 

10import kimpy 

11 

12from .exceptions import KIMModelNotFound, KIMModelInitializationError, KimpyError 

13 

14 

15def check_call(f, *args, **kwargs): 

16 """ 

17 Call a kimpy function using its arguments and, if a RuntimeError is raised, 

18 catch it and raise a KimpyError with the exception's message. 

19 

20 (Starting with kimpy 2.0.0, a RuntimeError is the only exception type raised 

21 when something goes wrong.) 

22 """ 

23 try: 

24 return f(*args, **kwargs) 

25 except RuntimeError as e: 

26 raise KimpyError(f'Calling kimpy function "{f.__name__}" failed:\n {str(e)}') 

27 

28 

29def check_call_wrapper(func): 

30 @functools.wraps(func) 

31 def myfunc(*args, **kwargs): 

32 return check_call(func, *args, **kwargs) 

33 

34 return myfunc 

35 

36 

37# kimpy methods 

38collections_create = functools.partial(check_call, kimpy.collections.create) 

39model_create = functools.partial(check_call, kimpy.model.create) 

40simulator_model_create = functools.partial(check_call, kimpy.simulator_model.create) 

41get_species_name = functools.partial(check_call, kimpy.species_name.get_species_name) 

42 

43# kimpy attributes (here to avoid importing kimpy in higher-level modules) 

44collection_item_type_portableModel = kimpy.collection_item_type.portableModel 

45 

46 

47class ModelCollections: 

48 """ 

49 KIM Portable Models and Simulator Models are installed/managed into 

50 different "collections". In order to search through the different 

51 KIM API model collections on the system, a corresponding object must 

52 be instantiated. For more on model collections, see the KIM API's 

53 install file: 

54 https://github.com/openkim/kim-api/blob/master/INSTALL 

55 """ 

56 

57 def __init__(self): 

58 self.collection = collections_create() 

59 

60 def __enter__(self): 

61 return self 

62 

63 def __exit__(self, exc_type, value, traceback): 

64 pass 

65 

66 def get_item_type(self, model_name): 

67 try: 

68 model_type = check_call(self.collection.get_item_type, model_name) 

69 except KimpyError: 

70 msg = ( 

71 "Could not find model {} installed in any of the KIM API model " 

72 "collections on this system. See " 

73 "https://openkim.org/doc/usage/obtaining-models/ for instructions on " 

74 "installing models.".format(model_name) 

75 ) 

76 raise KIMModelNotFound(msg) 

77 

78 return model_type 

79 

80 @property 

81 def initialized(self): 

82 return hasattr(self, "collection") 

83 

84 

85class PortableModel: 

86 """Creates a KIM API Portable Model object and provides a minimal interface to it""" 

87 

88 def __init__(self, model_name, debug): 

89 self.model_name = model_name 

90 self.debug = debug 

91 

92 # Create KIM API Model object 

93 units_accepted, self.kim_model = model_create( 

94 kimpy.numbering.zeroBased, 

95 kimpy.length_unit.A, 

96 kimpy.energy_unit.eV, 

97 kimpy.charge_unit.e, 

98 kimpy.temperature_unit.K, 

99 kimpy.time_unit.ps, 

100 self.model_name, 

101 ) 

102 

103 if not units_accepted: 

104 raise KIMModelInitializationError( 

105 "Requested units not accepted in kimpy.model.create" 

106 ) 

107 

108 if self.debug: 

109 l_unit, e_unit, c_unit, te_unit, ti_unit = check_call( 

110 self.kim_model.get_units 

111 ) 

112 print("Length unit is: {}".format(l_unit)) 

113 print("Energy unit is: {}".format(e_unit)) 

114 print("Charge unit is: {}".format(c_unit)) 

115 print("Temperature unit is: {}".format(te_unit)) 

116 print("Time unit is: {}".format(ti_unit)) 

117 print() 

118 

119 def __enter__(self): 

120 return self 

121 

122 def __exit__(self, exc_type, value, traceback): 

123 pass 

124 

125 def get_model_supported_species_and_codes(self): 

126 """Get all of the supported species for this model and their 

127 corresponding integer codes that are defined in the KIM API 

128 

129 Returns 

130 ------- 

131 species : list of str 

132 Abbreviated chemical symbols of all species the mmodel 

133 supports (e.g. ["Mo", "S"]) 

134 

135 codes : list of int 

136 Integer codes used by the model for each species (order 

137 corresponds to the order of ``species``) 

138 """ 

139 species = [] 

140 codes = [] 

141 num_kim_species = kimpy.species_name.get_number_of_species_names() 

142 

143 for i in range(num_kim_species): 

144 species_name = get_species_name(i) 

145 species_support, code = self.get_species_support_and_code(species_name) 

146 

147 if species_support: 

148 species.append(str(species_name)) 

149 codes.append(code) 

150 

151 return species, codes 

152 

153 @check_call_wrapper 

154 def compute(self, compute_args_wrapped, release_GIL): 

155 return self.kim_model.compute(compute_args_wrapped.compute_args, release_GIL) 

156 

157 @check_call_wrapper 

158 def get_species_support_and_code(self, species_name): 

159 return self.kim_model.get_species_support_and_code(species_name) 

160 

161 @check_call_wrapper 

162 def get_influence_distance(self): 

163 return self.kim_model.get_influence_distance() 

164 

165 @check_call_wrapper 

166 def get_neighbor_list_cutoffs_and_hints(self): 

167 return self.kim_model.get_neighbor_list_cutoffs_and_hints() 

168 

169 def compute_arguments_create(self): 

170 return ComputeArguments(self, self.debug) 

171 

172 @property 

173 def initialized(self): 

174 return hasattr(self, "kim_model") 

175 

176 

177class ComputeArguments: 

178 """ 

179 Creates a KIM API ComputeArguments object from a KIM Portable Model object and 

180 configures it for ASE. A ComputeArguments object is associated with a KIM Portable 

181 Model and is used to inform the KIM API of what the model can compute. It is also 

182 used to register the data arrays that allow the KIM API to pass the atomic 

183 coordinates to the model and retrieve the corresponding energy and forces, etc. 

184 """ 

185 

186 def __init__(self, kim_model_wrapped, debug): 

187 self.kim_model_wrapped = kim_model_wrapped 

188 self.debug = debug 

189 

190 # Create KIM API ComputeArguments object 

191 self.compute_args = check_call( 

192 self.kim_model_wrapped.kim_model.compute_arguments_create 

193 ) 

194 

195 # Check compute arguments 

196 kimpy_arg_name = kimpy.compute_argument_name 

197 num_arguments = kimpy_arg_name.get_number_of_compute_argument_names() 

198 if self.debug: 

199 print("Number of compute_args: {}".format(num_arguments)) 

200 

201 for i in range(num_arguments): 

202 name = check_call(kimpy_arg_name.get_compute_argument_name, i) 

203 dtype = check_call(kimpy_arg_name.get_compute_argument_data_type, name) 

204 

205 arg_support = self.get_argument_support_status(name) 

206 

207 if self.debug: 

208 print( 

209 "Compute Argument name {:21} is of type {:7} and has support " 

210 "status {}".format(*[str(x) for x in [name, dtype, arg_support]]) 

211 ) 

212 

213 # See if the model demands that we ask it for anything other than energy and 

214 # forces. If so, raise an exception. 

215 if arg_support == kimpy.support_status.required: 

216 if ( 

217 name != kimpy.compute_argument_name.partialEnergy 

218 and name != kimpy.compute_argument_name.partialForces 

219 ): 

220 raise KIMModelInitializationError( 

221 "Unsupported required ComputeArgument {}".format(name) 

222 ) 

223 

224 # Check compute callbacks 

225 callback_name = kimpy.compute_callback_name 

226 num_callbacks = callback_name.get_number_of_compute_callback_names() 

227 if self.debug: 

228 print() 

229 print("Number of callbacks: {}".format(num_callbacks)) 

230 

231 for i in range(num_callbacks): 

232 name = check_call(callback_name.get_compute_callback_name, i) 

233 

234 support_status = self.get_callback_support_status(name) 

235 

236 if self.debug: 

237 print( 

238 "Compute callback {:17} has support status {}".format( 

239 str(name), support_status 

240 ) 

241 ) 

242 

243 # Cannot handle any "required" callbacks 

244 if support_status == kimpy.support_status.required: 

245 raise KIMModelInitializationError( 

246 "Unsupported required ComputeCallback: {}".format(name) 

247 ) 

248 

249 @check_call_wrapper 

250 def set_argument_pointer(self, compute_arg_name, data_object): 

251 return self.compute_args.set_argument_pointer(compute_arg_name, data_object) 

252 

253 @check_call_wrapper 

254 def get_argument_support_status(self, name): 

255 return self.compute_args.get_argument_support_status(name) 

256 

257 @check_call_wrapper 

258 def get_callback_support_status(self, name): 

259 return self.compute_args.get_callback_support_status(name) 

260 

261 @check_call_wrapper 

262 def set_callback(self, compute_callback_name, callback_function, data_object): 

263 return self.compute_args.set_callback( 

264 compute_callback_name, callback_function, data_object 

265 ) 

266 

267 @check_call_wrapper 

268 def set_callback_pointer(self, compute_callback_name, callback, data_object): 

269 return self.compute_args.set_callback_pointer( 

270 compute_callback_name, callback, data_object 

271 ) 

272 

273 def update( 

274 self, num_particles, species_code, particle_contributing, coords, energy, forces 

275 ): 

276 """Register model input and output in the kim_model object.""" 

277 compute_arg_name = kimpy.compute_argument_name 

278 set_argument_pointer = self.set_argument_pointer 

279 

280 set_argument_pointer(compute_arg_name.numberOfParticles, num_particles) 

281 set_argument_pointer(compute_arg_name.particleSpeciesCodes, species_code) 

282 set_argument_pointer( 

283 compute_arg_name.particleContributing, particle_contributing 

284 ) 

285 set_argument_pointer(compute_arg_name.coordinates, coords) 

286 set_argument_pointer(compute_arg_name.partialEnergy, energy) 

287 set_argument_pointer(compute_arg_name.partialForces, forces) 

288 

289 if self.debug: 

290 print("Debug: called update_kim") 

291 print() 

292 

293 

294class SimulatorModel: 

295 """Creates a KIM API Simulator Model object and provides a minimal 

296 interface to it. This is only necessary in this package in order to 

297 extract any information about a given simulator model because it is 

298 generally embedded in a shared object. 

299 """ 

300 

301 def __init__(self, model_name): 

302 # Create a KIM API Simulator Model object for this model 

303 self.model_name = model_name 

304 self.simulator_model = simulator_model_create(self.model_name) 

305 

306 # Need to close template map in order to access simulator model metadata 

307 self.simulator_model.close_template_map() 

308 

309 def __enter__(self): 

310 return self 

311 

312 def __exit__(self, exc_type, value, traceback): 

313 pass 

314 

315 @property 

316 def simulator_name(self): 

317 simulator_name, _ = self.simulator_model.get_simulator_name_and_version() 

318 return simulator_name 

319 

320 @property 

321 def num_supported_species(self): 

322 num_supported_species = self.simulator_model.get_number_of_supported_species() 

323 if num_supported_species == 0: 

324 raise KIMModelInitializationError( 

325 "Unable to determine supported species of simulator model {}.".format( 

326 self.model_name 

327 ) 

328 ) 

329 else: 

330 return num_supported_species 

331 

332 @property 

333 def supported_species(self): 

334 supported_species = [] 

335 for spec_code in range(self.num_supported_species): 

336 species = check_call(self.simulator_model.get_supported_species, spec_code) 

337 supported_species.append(species) 

338 

339 return tuple(supported_species) 

340 

341 @property 

342 def num_metadata_fields(self): 

343 return self.simulator_model.get_number_of_simulator_fields() 

344 

345 @property 

346 def metadata(self): 

347 sm_metadata_fields = {} 

348 for field in range(self.num_metadata_fields): 

349 extent, field_name = check_call( 

350 self.simulator_model.get_simulator_field_metadata, field 

351 ) 

352 sm_metadata_fields[field_name] = [] 

353 for ln in range(extent): 

354 field_line = check_call( 

355 self.simulator_model.get_simulator_field_line, field, ln 

356 ) 

357 sm_metadata_fields[field_name].append(field_line) 

358 

359 return sm_metadata_fields 

360 

361 @property 

362 def supported_units(self): 

363 try: 

364 supported_units = self.metadata["units"][0] 

365 except (KeyError, IndexError): 

366 raise KIMModelInitializationError( 

367 "Unable to determine supported units of simulator model {}.".format( 

368 self.model_name 

369 ) 

370 ) 

371 

372 return supported_units 

373 

374 @property 

375 def atom_style(self): 

376 """ 

377 See if a 'model-init' field exists in the SM metadata and, if 

378 so, whether it contains any entries including an "atom_style" 

379 command. This is specific to LAMMPS SMs and is only required 

380 for using the LAMMPSrun calculator because it uses 

381 lammps.inputwriter to create a data file. All other content in 

382 'model-init', if it exists, is ignored. 

383 """ 

384 atom_style = None 

385 for ln in self.metadata.get("model-init", []): 

386 if ln.find("atom_style") != -1: 

387 atom_style = ln.split()[1] 

388 

389 return atom_style 

390 

391 @property 

392 def model_defn(self): 

393 return self.metadata["model-defn"] 

394 

395 @property 

396 def initialized(self): 

397 return hasattr(self, "simulator_model")