Coverage for /builds/kinetik161/ase/ase/calculators/kim/calculators.py: 23.26%
86 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
1import os
2import re
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
9from .exceptions import KIMCalculatorError
10from .kimmodel import KIMModelCalculator
13def KIMCalculator(model_name, options, debug):
14 """
15 Used only for Portable Models
16 """
18 options_not_allowed = ["modelname", "debug"]
20 _check_conflict_options(options, options_not_allowed,
21 simulator="kimmodel")
23 return KIMModelCalculator(model_name, debug=debug, **options)
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"""
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 = {}
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
47 # Set units to prevent them from defaulting to metal
48 parameters["units"] = supported_units
50 parameters["model_init"] = [
51 f"kim_init {model_name} {supported_units}{os.linesep}"
52 ]
54 parameters["kim_interactions"] = "kim_interactions {}{}".format(
55 (" ").join(supported_species), os.linesep
56 )
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)
77 return parameters
79 options_not_allowed = ["parameters", "files", "specorder",
80 "keep_tmp_files"]
82 _check_conflict_options(options, options_not_allowed,
83 simulator="lammpsrun")
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)
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")
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)
100 return LAMMPS(
101 **parameters, specorder=supported_species, keep_tmp_files=debug,
102 **options
103 )
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 ]
119 _check_conflict_options(options, options_not_allowed,
120 simulator="lammpslib")
121 # Set up LAMMPS header commands lookup table
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]
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)
133 # Assign atom types to species
134 atom_types = {}
135 for i_s, s in enumerate(supported_species):
136 atom_types[s] = i_s + 1
138 kim_interactions = [
139 "kim_interactions {}".format(
140 (" ").join(supported_species))]
142 # Return LAMMPSlib calculator
143 return LAMMPSlib(
144 lammps_header=model_init,
145 lammps_name=None,
146 lmpcmds=kim_interactions,
147 post_changebox_cmds=kim_interactions,
148 atom_types=atom_types,
149 log_file="lammps.log",
150 keep_alive=True,
151 **options
152 )
155def ASAPCalculator(model_name, model_type, options, **kwargs):
156 """
157 Can be used with either Portable Models or Simulator Models
158 """
159 import asap3
161 options_not_allowed = {"pm": ["name", "verbose"], "sm": ["Params"]}
163 _check_conflict_options(
164 options,
165 options_not_allowed[model_type],
166 simulator="asap")
168 if model_type == "pm":
170 return asap3.OpenKIMcalculator(
171 name=model_name, verbose=kwargs["verbose"], **options
172 )
174 elif model_type == "sm":
175 model_defn = kwargs["model_defn"]
176 supported_units = kwargs["supported_units"]
178 # Verify units (ASAP models are expected to work with "ase" units)
179 if supported_units != "ase":
180 raise KIMCalculatorError(
181 'KIM Simulator Model units are "{}", but expected to '
182 'be "ase" for ASAP.'.format(supported_units)
183 )
185 # Check model_defn to make sure there's only one element in it
186 # that is a non-empty string
187 if len(model_defn) == 0:
188 raise KIMCalculatorError(
189 "model-defn is an empty list in metadata file of "
190 "Simulator Model {}".format(model_name)
191 )
192 elif len(model_defn) > 1:
193 raise KIMCalculatorError(
194 "model-defn should contain only one entry for an ASAP "
195 "model (found {} lines)".format(len(model_defn))
196 )
198 if "" in model_defn:
199 raise KIMCalculatorError(
200 "model-defn contains an empty string in metadata "
201 "file of Simulator "
202 "Model {}".format(model_name)
203 )
205 model_defn = model_defn[0].strip()
207 # Instantiate calculator from ASAP. Currently, this must be one of:
208 # (1) EMT
209 # (2) EMT(EMTRasmussenParameters)
210 # (3) EMT(EMTMetalGlassParameters)
211 model_defn_is_valid = False
212 if model_defn.startswith("EMT"):
213 # Pull out potential parameters
214 mobj = re.search(r"\(([A-Za-z0-9_\(\)]+)\)", model_defn)
215 if mobj is None:
216 asap_calc = asap3.EMT()
217 else:
218 pp = mobj.group(1)
220 # Currently we only supported two specific EMT models
221 # that are built into ASAP
222 if pp.startswith("EMTRasmussenParameters"):
223 asap_calc = asap3.EMT(
224 parameters=asap3.EMTRasmussenParameters())
225 model_defn_is_valid = True
226 elif pp.startswith("EMTMetalGlassParameters"):
227 asap_calc = asap3.EMT(
228 parameters=asap3.EMTMetalGlassParameters())
229 model_defn_is_valid = True
231 if not model_defn_is_valid:
232 raise KIMCalculatorError(
233 'Unknown model "{}" requested for simulator asap.'.format(
234 model_defn)
235 )
237 # Disable undocumented feature for the EMT self.calculators to
238 # take the energy of an isolated atoms as zero. (Otherwise it
239 # is taken to be that of perfect FCC.)
240 asap_calc.set_subtractE0(False)
242 return asap_calc
245def _check_conflict_options(options, options_not_allowed, simulator):
246 """Check whether options intended to be passed to a given calculator
247 are allowed. Some options are not allowed because they must be
248 set internally in this package."""
249 s1 = set(options)
250 s2 = set(options_not_allowed)
251 common = s1.intersection(s2)
253 if common:
254 options_in_not_allowed = ", ".join([f'"{s}"' for s in common])
256 msg = (
257 'Simulator "{}" does not support argument(s): '
258 '{} provided in "options", '
259 "because it is (they are) determined internally within the KIM "
260 "calculator".format(simulator, options_in_not_allowed)
261 )
263 raise KIMCalculatorError(msg)