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
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +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 = {s: i_s + 1 for i_s, s in enumerate(supported_species)}
135 kim_interactions = [
136 "kim_interactions {}".format(
137 (" ").join(supported_species))]
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 )
152def ASAPCalculator(model_name, model_type, options, **kwargs):
153 """
154 Can be used with either Portable Models or Simulator Models
155 """
156 import asap3
158 options_not_allowed = {"pm": ["name", "verbose"], "sm": ["Params"]}
160 _check_conflict_options(
161 options,
162 options_not_allowed[model_type],
163 simulator="asap")
165 if model_type == "pm":
167 return asap3.OpenKIMcalculator(
168 name=model_name, verbose=kwargs["verbose"], **options
169 )
171 elif model_type == "sm":
172 model_defn = kwargs["model_defn"]
173 supported_units = kwargs["supported_units"]
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 )
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 )
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 )
202 model_defn = model_defn[0].strip()
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)
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
228 if not model_defn_is_valid:
229 raise KIMCalculatorError(
230 'Unknown model "{}" requested for simulator asap.'.format(
231 model_defn)
232 )
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)
239 return asap_calc
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)
250 if common:
251 options_in_not_allowed = ", ".join([f'"{s}"' for s in common])
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 )
260 raise KIMCalculatorError(msg)