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
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3import os
4import re
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
11from .exceptions import KIMCalculatorError
12from .kimmodel import KIMModelCalculator
15def KIMCalculator(model_name, options, debug):
16 """
17 Used only for Portable Models
18 """
20 options_not_allowed = ["modelname", "debug"]
22 _check_conflict_options(options, options_not_allowed,
23 simulator="kimmodel")
25 return KIMModelCalculator(model_name, debug=debug, **options)
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"""
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 = {}
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
49 # Set units to prevent them from defaulting to metal
50 parameters["units"] = supported_units
52 parameters["model_init"] = [
53 f"kim_init {model_name} {supported_units}{os.linesep}"
54 ]
56 parameters["kim_interactions"] = "kim_interactions {}{}".format(
57 (" ").join(supported_species), os.linesep
58 )
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)
79 return parameters
81 options_not_allowed = ["parameters", "files", "specorder",
82 "keep_tmp_files"]
84 _check_conflict_options(options, options_not_allowed,
85 simulator="lammpsrun")
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)
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")
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)
102 return LAMMPS(
103 **parameters, specorder=supported_species, keep_tmp_files=debug,
104 **options
105 )
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 ]
121 _check_conflict_options(options, options_not_allowed,
122 simulator="lammpslib")
123 # Set up LAMMPS header commands lookup table
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]
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)
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))]
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 )
154def ASAPCalculator(model_name, model_type, options, **kwargs):
155 """
156 Can be used with either Portable Models or Simulator Models
157 """
158 import asap3
160 options_not_allowed = {"pm": ["name", "verbose"], "sm": ["Params"]}
162 _check_conflict_options(
163 options,
164 options_not_allowed[model_type],
165 simulator="asap")
167 if model_type == "pm":
169 return asap3.OpenKIMcalculator(
170 name=model_name, verbose=kwargs["verbose"], **options
171 )
173 elif model_type == "sm":
174 model_defn = kwargs["model_defn"]
175 supported_units = kwargs["supported_units"]
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 )
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 )
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 )
204 model_defn = model_defn[0].strip()
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)
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
230 if not model_defn_is_valid:
231 raise KIMCalculatorError(
232 'Unknown model "{}" requested for simulator asap.'.format(
233 model_defn)
234 )
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)
241 return asap_calc
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)
252 if common:
253 options_in_not_allowed = ", ".join([f'"{s}"' for s in common])
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 )
262 raise KIMCalculatorError(msg)