AMS driver and engines¶
The AMS driver is a new program introduced in the 2018 release that unifies the way in which different computational engines of Amsterdam Modelling Suite are called. You can find more information about the AMS driver in the corresponding part of the documentation.
Preparing input¶
Tip
Starting with AMS2024, you can also use PISA (Python Input System for AMS) to specify the input to AMS.
However, almost all PLAMS examples still use the input description described on this page.
Note
Input files handling in the AMS driver is case insensitive.
The input file for the AMS driver consists of keys and values organized in blocks and subblocks:
Task GeometryOptimization
GeometryOptimization
Convergence
Gradients 1.0e-4
End
End
Properties
NormalModes true
End
System
Atoms
C 0.00000000 0.00000000 0.00000000
H 0.63294000 -0.63294000 -0.63294000
H -0.63294000 0.63294000 -0.63294000
H 0.63294000 0.63294000 0.63294000
H -0.63294000 -0.63294000 0.63294000
End
End
Engine DFTB
Model DFTB3
ResourcesDir DFTB.org/3ob-3-1
EndEngine
Such a structure can be reflected in a natural way by a multi-level character of Settings
.
The example input file presented above can be generated by:
s = Settings()
#AMS driver input
s.input.ams.Task = 'GeometryOptimization'
s.input.ams.GeometryOptimization.Convergence.Gradients = 1.0e-4
s.input.ams.Properties.NormalModes = 'true'
#DFTB engine input
s.input.DFTB.Model = 'DFTB3'
s.input.DFTB.ResourcesDir = 'DFTB.org/3ob-3-1'
m = Molecule('methane.xyz')
j = AMSJob(molecule=m, settings=s)
j.run()
If an entry is a regular key-value pair it is printed in one line (like Task GeometryOptimization
above).
If an entry is a nested Settings
instance it is printed as a block and entries inside this instance correspond to the contents of that block.
One of the blocks is special: the engine block.
It defines the computational engine used to perform the task defined in all other blocks.
The contents of the engine block are not processed by the AMS driver, but rather passed to the corresponding engine instead.
Because of that every AMS input file can be seen as composed of two distinct parts: the engine input (everything inside the engine block) and the driver input (everything apart from the engine block).
That distinction is reflected by how a Settings
instance for AMSJob
is structured.
As we can see, the input
branch of job settings is divided into two branches: the ams
branch for the driver input and the DFTB
branch for the engine input.
Note
In general, PLAMS will use all the contents of the ams
branch (spelling not case-sensitive) to construct the driver input and the contents of every other branch to construct a separate engine block with the same name as the branch (like DFTB
in the example above).
In the present moment only applications with a single engine block are implemented in the AMS driver, but that will most likely change in the near future.
The contents of each branch of myjob.settings.input
are translated to a string using the same logic:
Entries within each block (including the top level) are listed in the alphabetical order.
Both keys and values are kept in their original case.
Strings used as values can contain spaces and all kinds of special characters, including new lines. They are printed in an unchanged form in the input file.
If you need to put a key without any value, you can use
True
or an empty string as a value:s.input.ams.block.key = True s.input.ams.otherkey = '' ### translates to: block key end otherkey
If a value of a key is
False
orNone
the key is omitted.En empty
Settings
instance produces an empty block:s.input.ams.emptyblock = Settings() s.input.ams.otherblock #short syntax equivalent to the line above ### translates to: emptyblock end otherblock end
More instances of the same key within one block can be achieved by using a list of values instead of a single value:
s.input.ams.constraints.atom = [1,5,4] s.input.ams.constraints.block = ['ligand', 'residue'] ### translates to: constraints atom 1 atom 5 atom 4 block ligand block residue end
Some blocks require (or allow) something to be put in the header line, next to the block name. Special key
_h
is helpful in these situations:s.input.ams.block._h = 'header=very important' s.input.ams.block.key1 = 'value1' s.input.ams.block.key2 = 'value2' ### translates to: someblock header=very important key1 value1 key2 value2 end
Another kind of special key can be used to override the default alphabetic ordering of entries within a block, or just to insert arbitrary strings into the block:
s.input.ams.block._1 = 'entire line that has to be the first line of block' s.input.ams.block._2 = 'second line' s.input.ams.block._4 = 'I will not be printed' s.input.ams.block.key1 = 'value1' s.input.ams.block.key2 = 'value2' ### translates to: block entire line that has to be the first line of block second line key1 value1 key2 value2 end
If a value of a key needs to be a path to some KF file with results of a previous AMS calculation, an instance of
AMSJob
orAMSResults
(or directlyKFFile
) can be used (seeAMSJob.get_input()
for details):oldjob = AMSJob(...) oldjob.run() newjob = AMSJob(...) newjob.settings.input.ams.loadsystem.file = oldjob newjob.settings.input.ams.loadengine = (oldjob, 'dftb') ### translates to: loadengine /home/user/plams_workdir/oldjob/dftb.rkf loadsystem file = /home/user/plams_workdir/oldjob/ams.rkf end
Convert AMS text-style input to a Settings object (this requires that the SCM python package is installed):
text = ''' Task GeometryOptimization Engine DFTB Model GFN1-xTB EndEngine ''' sett = AMSJob.from_input(text).settings print(sett) # output: input: dftb: model: GFN1-xTB ams: task: GeometryOptimization
Note
The algorithm translating Settings
contents into an input file does not check the correctness of the given data - it simply takes keys and values from Settings
and prints them in the text file.
Due to that you are not going to be warned if you make a typo, use a wrong keyword or improper syntax.
Preparing runscript¶
Runscripts for the AMS driver are very simple (see AMSJob.get_runscript()
).
The only adjustable option (apart from usual pre
, post
, shebang
and stdout_redirect
which are common for all single jobs) is myjob.settings.runscript.nproc
, indicating the number of parallel processes to run AMS with (like with -n
flag or NSCM
environmental variable).
Molecule handling¶
There are several ways in which the description of the simulated system can be supplied to AMSJob
.
The most convenient one is simply by passing a Molecule
instance:
mol = Molecule('/path/to/some/file.xyz')
myjob = AMSJob(name='test', molecule=mol, settings=...)
or:
mol = Molecule('/path/to/some/file.xyz')
myjob = AMSJob(...)
myjob.molecule = mol
Note
Instead of passing a Molecule
object to AMSJob
, you have the option to use a Chemical System as well.
A Molecule
instance stored as the molecule
attribute is automatically processed during the input file preparation and printed in the proper format (see AMS manual for details).
Various details of this process can be adjusted based on attributes of the supplied Molecule
.
If mol.lattice
is nonempty, the information about periodicity vectors is printed to the lattice
subblock of the system
block.
If the supplied lattice consists of 1 or 2 vectors that do not follow the convention requied by AMS (1D – vector aligned with X axis; 2D – vectors aligned with XY plane) the whole system is rotated to meet these criteria.
If mol.properties.charge
exists, it is used as the charge
key in the system
block.
Moreover, each Atom
present in the supplied Molecule
has its own properties
attribute that can be used to adjust the details of the line generated for this atom in the atoms
block:
The atomic symbol is generated based on the atomic number stored in the
atnum
attribute of theAtom
. The atomic number of 0 corresponds to the “dummy atom” for which the symbol is empty.If
atom.properties.ghost
exists and isTrue
, the atomic symbol is prefixed withGh.
.If
atom.properties.name
exists, the name is added after the atomic symbol, separated by a single dot.Leading, trailing and double dots are removed from the atomic symbol.
If
atom.properties.suffix
exists, it is placed at the end of the line, after the numerical coordinates (it should ba a string)
Example:
mol = Molecule('xyz/Ethanol.xyz')
mol[1].properties.ghost = True
mol[2].properties.name = 'D'
mol[3].properties.ghost = True
mol[3].properties.name = 'T'
mol[4].properties.atnum = 0
mol[4].properties.name = 'J.XYZ'
mol[5].properties.atnum = 0
mol[5].properties.name = 'J.ASD'
mol[5].properties.ghost = True
mol[6].properties.suffix = 'whatever text'
myjob = AMSJob(molecule=mol)
The corresponding fragment of the input file produced by the above code:
system
atoms
1 Gh.C 0.01247 0.02254 1.08262
2 C.D -0.00894 -0.01624 -0.43421
3 Gh.H.T -0.49334 0.93505 1.44716
4 J.XYZ 1.05522 0.04512 1.44808
5 Gh.J.ASD -0.64695 -1.12346 2.54219
6 H 0.50112 -0.91640 -0.80440 whatever text
7 H 0.49999 0.86726 -0.84481
8 H -1.04310 -0.02739 -0.80544
9 O -0.66442 -1.15471 1.56909
end
end
Another, more cumbersome way to provide the system information to AMSJob
is to manually populate the system
block in job settings:
s = Settings()
s.input.ams.system.atoms._1 = 'H 0.0 0.0 0.0'
s.input.ams.system.atoms._2 = 'O 1.0 0.0 0.0'
s.input.ams.system.charge = 1.0
#other settings adjustments
myjob = AMSJob(settings=s)
An alternative way of supplying molecular coordinates is to use the GeometryFile
key in the system
block:
s = Settings()
s.input.ams.system.geometryfile = '/path/to/some/file.xyz'
#other settings adjustments
myjob = AMSJob(settings=s)
Currently only the extended XYZ format is supported.
Finally, one could use the LoadSystem
top-level key and point to an existing .rkf
file with results of some previous calculation:
s = Settings()
s.input.loadsystem = '/path/to/some/ams.rkf'
#other settings adjustments
myjob = AMSJob(settings=s)
Multiple molecules¶
The AMS driver allows multiple occurrences of the system
block in the input file.
Different system
blocks are distinguished by their names defined in the header of the block:
system protein
atoms
...
end
end
system ligand
atoms
...
end
end
The system without such a name is considered the main system.
Multiple systems can be used in AMSJob
by setting the molecule
attribute to a dictionary, instead of a single Molecule
.
Such a dictionary should have strings as keys and Molecule
instances as values.
The main system should have ''
(an empty string) as a key.
Other methods of providing the contents of the system
block mentioned above can also be used to provide multiple system
blocks.
myjob.settings.input.ams.system
can be a list containing multiple Settings
instances, one for each system.
Each such instance can be have manually filled atoms
block or use the geometryfile
key.
Special header _h
key can be used to set headers and hence names of different system
blocks.
Multiple instances of the LoadSystem
key (also provided as a list, also with _h
headers) can also be used.
All the methods mentioned above (molecule
attribute, GeometryFile
, LoadSystem
, manual system
block preparation) can be combined in any configuration.
In case of a conflict, the data stored in settings.input.ams.system
takes precedence over molecule
.
It is, however, the user’s responsibility to make sure that among all the systems provided there is exactly one main system (without a name).
AMSJob API¶
- class AMSJob(name='plamsjob', molecule=None, settings=None, depend=None)[source]¶
A class representing a single computation with AMS driver. The corresponding results type is
AMSResults
.- run(jobrunner=None, jobmanager=None, watch=False, **kwargs)[source]¶
Run the job using jobmanager and jobrunner (or defaults, if
None
).If watch is set to
True
, the contents of the AMS driver logfile will be forwarded line by line to the PLAMS logfile (and stdout), allowing for an easier monitoring of the running job. Not that the forwarding of the AMS driver logfile will make the call to this method block until the job’s execution has finished, even when using a parallelJobRunner
.Other keyword arguments (**kwargs) are stored in
run
branch of job’s settings.Returned value is the
AMSResults
instance associated with this job.
- get_input()[source]¶
Generate the input file. This method is just a wrapper around
_serialize_input()
.Each instance of
AMSJob
orAMSResults
present as a value insettings.input
branch is replaced with an absolute path toams.rkf
file of that job.If you need to use a path to some engine specific
.rkf
file rather than the mainams.rkf
file, you can to it by supplying a tuple(x, name)
wherex
is an instance ofAMSJob
orAMSResults
andname
is a string with the name of the.rkf
file you want. For example,(myjob, 'dftb')
will transform to the absolute path todftb.rkf
file inmyjob
’s folder, if such a file is present.Instances of
KFFile
are replaced with absolute paths to corresponding files.
- get_runscript()[source]¶
Generate the runscript. Returned string is of the form:
unset AMS_SWITCH_LOGFILE_AND_STDOUT AMS_JOBNAME=jobname AMS_RESULTSDIR=. $AMSBIN/ams [-n nproc] --input=jobname.in [>jobname.out]
-n
flag is added ifsettings.runscript.nproc
exists.[>jobname.out]
is used based onsettings.runscript.stdout_redirect
. Ifsettings.runscript.preamble_lines
exists, those lines will be added to the runscript verbatim before the execution of AMS. Ifsettings.runscript.postamble_lines
exists, those lines will be added to the runscript verbatim after the execution of AMS.
- check()[source]¶
Check if
termination status
variable fromGeneral
section of main KF file equalsNORMAL TERMINATION
.
- get_errormsg()[source]¶
Tries to get an error message for a failed job. This method returns
None
for successful jobs.
- hash_input()[source]¶
Calculate the hash of the input file.
All instances of
AMSJob
orAMSResults
present as values insettings.input
branch are replaced with hashes of corresponding job’s inputs. Instances ofKFFile
are replaced with absolute paths to corresponding files.
- get_task()[source]¶
Returns the AMS Task from the job’s settings. If it does not exist, returns None.
- classmethod load_external(path, settings=None, molecule=None, finalize=False, fmt='ams')[source]¶
Load an external job from path.
In this context an “external job” is an execution of some external binary that was not managed by PLAMS, and hence does not have a
.dill
file. It can also be used in situations where the execution was started with PLAMS, but the Python process was terminated before the execution finished, resulting in steps 9-12 of Running a job not happening.All the files produced by your computation should be placed in one folder and path should be the path to this folder or a file in this folder. The name of the folder is used as a job name. Input, output, error and runscript files, if present, should have names defined in
_filenames
class attribute (usually[jobname].in
,[jobname].out
,[jobname].err
and[jobname].run
). It is not required to supply all these files, but in most cases one would like to use at least the output file, in order to use methods likegrep_output()
orget_output_chunk()
. If path is an instance of an AMSJob, that instance is returned.This method is a class method, so it is called via class object and it returns an instance of that class:
>>> a = AMSJob.load_external(path='some/path/jobname') >>> type(a) scm.plams.interfaces.adfsuite.ams.AMSJob
You can supply
Settings
andMolecule
instances as settings and molecule parameters, they will end up attached to the returned job instance. If you don’t do this, PLAMS will try to recreate them automatically using methodsrecreate_settings()
andrecreate_molecule()
of the correspondingResults
subclass. If noSettings
instance is obtained in either way, the defaults fromconfig.job
are copied.You can set the finalize parameter to
True
if you wish to run the whole_finalize()
on the newly created job. In that case PLAMS will perform the usualcheck()
to determine the job status (successful or failed), followed by cleaning of the job folder (Cleaning job folder),postrun()
and pickling (Pickling). If finalize isFalse
, the status of the returned job is copied.- fmtstr
One of ‘ams’, ‘qe’, ‘vasp’, or ‘any’.
‘ams’: load a finished AMS job
‘qe’: convert a Quantum ESPRESSO .out file to ams.rkf and qe.rkf and load from those files
‘vasp’: convert a VASP OUTCAR file to ams.rkf and vasp.rkf and load from those files (see more below)
‘any’: auto-detect the format.
This method can also be used to convert a finished VASP job to an AMSJob. If you supply the path to a folder containing OUTCAR, then a subdirectory will be created in this folder called AMSJob. In the AMSJob subdirectory, two files will be created: ams.rkf and vasp.rkf, that contain some of the results from the VASP calculation. If the AMSJob subdirectory already exists, the existing ams.rkf and vasp.rkf files will be reused. NOTE: the purpose of loading VASP data this way is to let you call for example job.results.get_energy() etc., not to run new VASP calculations!
- classmethod from_input(text_input, **kwargs)[source]¶
Creates an AMSJob from AMS-style text input. This function requires that the SCM Python package is installed (if not, it will raise an ImportError).
text_input : a multi-line string
Returns: An AMSJob instance
Example:
text = ''' Task GeometryOptimization Engine DFTB Model GFN1-xTB EndEngine ''' job = AMSJob.from_input(text)
Note
If molecule is included in the keyword arguments to this method, the text_input may not contain any System blocks. In other words, the molecules to be used either need to come from the text_input, or the keyword argument, but not both.
If settings is included in the keyword arguments to this method, the
Settings
created from the text_input will be soft updated with the settings from the keyword argument. In other word, the text_input takes precedence over the settings keyword argument.
- classmethod from_inputfile(filename, heredoc_delimit='eor', **kwargs)[source]¶
Construct an
AMSJob
instance from an AMS inputfile or runfile.If a runscript is provide than this method will attempt to extract the input file based on the heredoc delimiter (see heredoc_delimit).
- static settings_to_mol(s)[source]¶
Pop the s.input.ams.system block from a settings instance and convert it into a dictionary of molecules.
The provided settings should be in the same style as the ones produced by the SCM input parser. Dictionary keys are taken from the header of each system block. The existing s.input.ams.system block is removed in the process, assuming it was present in the first place.
AMSResults API¶
- class AMSResults(*args, **kwargs)[source]¶
A specialized
Results
subclass for accessing the results ofAMSJob
.- collect()[source]¶
Collect files present in the job folder. Use parent method from
Results
, then create an instance ofKFFile
for each.rkf
file present in the job folder. Collect these files inrkfs
dictionary, with keys being filenames without.rkf
extension.The information about
.rkf
files generated by engines is taken from the mainams.rkf
file.This method is called automatically during the final part of the job execution and there is no need to call it manually.
- refresh()[source]¶
Refresh the contents of
files
list.Use the parent method from
Results
, then look atKFFile
instances present inrkfs
dictionary and check if they point to existing files. If not, try to reinstantiate them with current job path (that can happen while loading a pickled job after the entire job folder was moved).
- engine_names()[source]¶
Return a list of all names of engine specific
.rkf
files. The identifier of the main result file ('ams'
) is not present in the returned list, only engine specific names are listed.
- read_hybrid_term_rkf(section, variable, term, file='engine')[source]¶
Reads a Hybrid-termX-subengine.rkf file.
The engine.rkf file contains a section EngineResults with Files(1), Files(2) etc. that point to the individual term .rkf files.
This method reads the corresponding individual term .rkf file.
Example: job.results.read_hybrid_term_rkf(“AMSResults”, “Energy”, file=”engine”, term=1)
- rkfpath(file='ams')[source]¶
Return the absolute path of a chosen
.rkf
file.The file argument should be the identifier of the file to read. It defaults to
'ams'
. To access a file calledsomething.rkf
you need to call this function withfile='something'
. If there exists only one engine results.rkf
file, you can call this function withfile='engine'
to access this file.
- readrkf(section, variable, file='ams')[source]¶
Read data from section/variable of a chosen
.rkf
file.The file argument should be the identifier of the file to read. It defaults to
'ams'
. To access a file calledsomething.rkf
you need to call this function withfile='something'
. If there exists only one engine results.rkf
file, you can call this function withfile='engine'
to access this file.The type of the returned value depends on the type of variable defined inside KF file. It can be: single int, list of ints, single float, list of floats, single boolean, list of booleans or string.
Note
If arguments section or variable are incorrect (not present in the chosen file), the returned value is
None
. Please mind the fact that KF files are case sensitive.
- read_rkf_section(section, file='ams')[source]¶
Return a dictionary with all variables from a given section of a chosen
.rkf
file.The file argument should be the identifier of the file to read. It defaults to
'ams'
. To access a file calledsomething.rkf
you need to call this function withfile='something'
. If there exists only one engine results.rkf
file, you can call this function withfile='engine'
to access this file.Note
If section is not present in the chosen file, the returned value is an empty dictionary. Please mind the fact that KF files are case sensitive.
- get_rkf_skeleton(file='ams')[source]¶
Return a dictionary with the structure of a chosen
.rkf
file. Each key corresponds to a section name with the value being a set of variable names present in that section.The file argument should be the identifier of the file to read. It defaults to
'ams'
. To access a file calledsomething.rkf
you need to call this function withfile='something'
. If there exists only one engine results.rkf
file, you can call this function withfile='engine'
to access this file.
- get_molecule(section, file='ams')[source]¶
Return a
Molecule
instance stored in a given section of a chosen.rkf
file.The file argument should be the identifier of the file to read. It defaults to
'ams'
. To access a file calledsomething.rkf
you need to call this function withfile='something'
. If there exists only one engine results.rkf
file, you can call this function withfile='engine'
to access this file.All data used by this method is taken from the chosen
.rkf
file. Themolecule
attribute of the corresponding job is ignored.
- get_system(section, file='ams')[source]¶
Return a
ChemicalSystem
instance stored in a given section of a chosen.rkf
file.The file argument should be the identifier of the file to read. It defaults to
'ams'
. To access a file calledsomething.rkf
you need to call this function withfile='something'
. If there exists only one engine results.rkf
file, you can call this function withfile='engine'
to access this file.All data used by this method is taken from the chosen
.rkf
file. Themolecule
attribute of the corresponding job is ignored.Note that
ChemicalSystem
is only available within AMS python. If unavailable, the call will raise an error.
- get_input_molecule()[source]¶
Return a
Molecule
instance with the initial coordinates.All data used by this method is taken from
ams.rkf
file. Themolecule
attribute of the corresponding job is ignored.
- get_input_system()[source]¶
Return a
ChemicalSystem
instance with the initial coordinates.All data used by this method is taken from
ams.rkf
file. Themolecule
attribute of the corresponding job is ignored.Note that
ChemicalSystem
is only available within AMS python. If unavailable, the call will raise an error.
- get_input_molecules()[source]¶
Return a dictionary mapping the name strings from the AMS input file to
Molecule
instances.The main molecule (aka the one that did not have a string in the block header in the input file) will be returned under the key of the empty string “”. All data used by this method is taken from
ams.rkf
file. Themolecule
attribute of the corresponding job is ignored.
- get_main_molecule()[source]¶
Return a
Molecule
instance with the final coordinates.All data used by this method is taken from
ams.rkf
file. Themolecule
attribute of the corresponding job is ignored.
- get_main_system()[source]¶
Return a
ChemicalSystem
instance with the final coordinates.All data used by this method is taken from
ams.rkf
file. Themolecule
attribute of the corresponding job is ignored.Note that
ChemicalSystem
is only available within AMS python. If unavailable, the call will raise an error.
- get_main_ase_atoms(get_results=False)[source]¶
Return an ase.Atoms instance with the final coordinates.
An alternative is to call toASE(results.get_main_molecule()) to convert a Molecule to ASE Atoms.
- get_resultsbool
If True, the returned Atoms will have a SinglePointCalculator as their calculator, allowing you to call .get_potential_energy(), .get_forces(), and .get_stress() on the returned Atoms object.
- get_history_molecule(step)[source]¶
Return a
Molecule
instance with coordinates taken from a particular step in theHistory
section ofams.rkf
file.All data used by this method is taken from
ams.rkf
file. Themolecule
attribute of the corresponding job is ignored.
- get_history_variables(history_section='History')[source]¶
Return a set of keynames stored in the specified history section of the
ams.rkf
file.The history_section argument should be a string representing the name of the history section (``History`` or ``MDHistory``)
- get_history_length(history_section='History')[source]¶
Returns the number of entries (nEntries) in the history section on the ams.rkf file.
- get_history_property(varname, history_section='History')[source]¶
Return the values of varname in the history section history_section.
- get_property_at_step(step, varname, history_section='History')[source]¶
Return the value of varname in the history section history_section at step *step.
- get_atomic_temperatures_at_step(step, history_section='MDHistory')[source]¶
Get all the atomic temperatures for step step
Note: Numbering of steps starts at 1
- get_band_structure(bands=None, unit='hartree', only_high_symmetry_points=False)[source]¶
Extracts the electronic band structure from DFTB or BAND calculations. The returned data can be plotted with
plot_band_structure
.Note: for unrestricted calculations bands 0, 2, 4, … are spin-up and bands 1, 3, 5, … are spin-down.
Returns:
x
,y_spin_up
,y_spin_down
,labels
,fermi_energy
x
: 1D array of floaty_spin_up
: 2D array of shape (len(x), len(bands)). Every column is a separate band. In units ofunit
y_spin_down
: 2D array of shape (len(x), len(bands)). Every column is a separate band. In units ofunit
. If the calculation was restricted this is identical to y_spin_up.labels
: 1D list of str of length len(x). If a point is not a high-symmetry point then the label is an empty string.fermi_energy
: float. The Fermi energy (in units ofunit
).Arguments below.
- bands: list of int or None
If None, all bands are returned. Note: the band indices start with 0.
- unit: str
Unit of the returned band energies
- only_high_symmetry_points: bool
Return only the first point of each edge.
- get_engine_results(engine=None)[source]¶
Return a dictionary with contents of
AMSResults
section from an engine results.rkf
file.The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_engine_properties(engine=None)[source]¶
Return a dictionary with all the entries from
Properties
section from an engine results.rkf
file.The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_energy(unit='hartree', engine=None)[source]¶
Return final energy, expressed in unit. The final energy is found in AMSResults%Energy of the engine rkf file. You can find the meaning of final energy in the engine documentation.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_energy_uncertainty(unit='hartree', engine=None)[source]¶
Return final energy uncertainty, expressed in unit. The final energy is found in AMSResults%EnergyU of the engine rkf file. You can find the meaning of final energy uncertainty in the engine documentation.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_gradients(energy_unit='hartree', dist_unit='bohr', engine=None)[source]¶
Return the gradients of the final energy, expressed in energy_unit / dist_unit.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_gradients_uncertainty(energy_unit='hartree', dist_unit='bohr', engine=None)[source]¶
Return the uncertainty of the gradients of the final energy, expressed in energy_unit / dist_unit.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_gradients_magnitude_uncertainty(energy_unit='hartree', dist_unit='bohr', engine=None)[source]¶
Return the uncertainty of the magnitude of the gradients of the final energy, expressed in energy_unit / dist_unit.
This is computed using error propagation. The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_stresstensor(engine=None)[source]¶
Return the final stress tensor, expressed in atomic units.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_hessian(engine=None)[source]¶
Return the Hessian matrix, i.e. the second derivative of the total energy with respect to the nuclear coordinates, expressed in atomic units.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_elastictensor(engine=None)[source]¶
Return the elastic tensor, expressed in atomic units.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_frequencies(unit='cm^-1', engine=None)[source]¶
Return a numpy array of vibrational frequencies, expressed in unit.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_frequency_spectrum(engine=None, broadening_type='gaussian', broadening_width=40, min_x=0, max_x=4000, x_spacing=0.5, post_process=None)[source]¶
Return the “frequency spectrum” (i.e. the frequencies broaden with intensity equal to 1). Units: frequencies are in cm-1, the intensities by the default are in mode counts units but post_process is equal to max_to_1 are in arbitrary units.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_force_constants(engine=None)[source]¶
Return a numpy array of force constants, expressed in atomic units (Hartree/bohr^2).
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_normal_modes(engine=None)[source]¶
Return a numpy array of normal modes with shape: (num_normal_modes, num_atoms, 3), expressed in dimensionless units.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_charges(engine=None)[source]¶
Return the atomic charges, expressed in atomic units.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_atom_types(engine=None)[source]¶
Return the atomic types, for each atom in the system.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_dipolemoment(engine=None)[source]¶
Return the electric dipole moment, expressed in atomic units.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_dipolegradients(engine=None)[source]¶
Return the nuclear gradients of the electric dipole moment, expressed in atomic units. This is a (3*numAtoms x 3) matrix.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_polarizability(engine=None)[source]¶
Return the polarizability, expressed in atomic units [(e*bohr)^2/hartree]. This is a (3 x 3) matrix.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_zero_point_energy(unit='hartree', engine=None)[source]¶
Return zero point energy, expressed in unit. The zero point energy is found in Vibrations%ZeroPointEnergy of the engine rkf file.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_ir_intensities(engine=None)[source]¶
Return the IR intensities in km/mol unit (kilometers/mol).
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_ir_spectrum(engine=None, broadening_type='gaussian', broadening_width=40, min_x=0, max_x=4000, x_spacing=0.5, post_process=None)[source]¶
Return the IR spectrum. Units: frequencies are in cm-1, the intensities by the default are in km/mol units (kilometers/mol) but if post_process is all_intensities_to_1 the units are in modes counts otherwise if equal to max_to_1 are in arbitrary units.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_n_spin(engine=None)[source]¶
n_spin is 1 in case of spin-restricted or spin-orbit coupling calculations, and 2 in case of spin-unrestricted calculations
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_orbital_energies(unit='Hartree', engine=None)[source]¶
Return the orbital energies in a numpy array of shape [nSpin,nOrbitals] (nSpin is 1 in case of spin-restricted or spin-orbit coupling and 2 in case of spin unrestricted)
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_orbital_occupations(engine=None)[source]¶
Return the orbital occupations in a numpy array of shape [nSpin,nOrbitals]. For spin restricted calculations, the occupations will be between 0 and 2. For spin unrestricted or spin-orbit coupling the values will be between 0 and 1.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_homo_energies(unit='Hartree', engine=None)[source]¶
Return the homo energies per spin as a numpy array of size [nSpin]. nSpin is 1 in case of spin-restricted or spin-orbit coupling and 2 in case of spin unrestricted. See also
are_orbitals_fractionally_occupied()
.The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_lumo_energies(unit='Hartree', engine=None)[source]¶
Return the lumo energies per spin as a numpy array of size [nSpin]. nSpin is 1 in case of spin-restricted or spin-orbit coupling and 2 in case of spin unrestricted. See also
are_orbitals_fractionally_occupied()
.The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_smallest_homo_lumo_gap(unit='Hartree', engine=None)[source]¶
Returns a float containing the smallest HOMO-LUMO gap irrespective of spin (i.e. min(LUMO) - max(HOMO)). See also
are_orbitals_fractionally_occupied()
.The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- are_orbitals_fractionally_occupied(engine=None)[source]¶
Returns a boolean indicating whether fractional occupations were detected. If that is the case, then the ‘HOMO’ and ‘LUMO’ labels are not well defined, since the demarcation between ‘occupied’ and ‘empty’ is somewhat arbitrary. See the AMSDriver documentation for more info.
The engine argument should be the identifier of the file you wish to read. To access a file called
something.rkf
you need to call this function withengine='something'
. The engine argument can be omitted if there’s only one engine results file in the job folder.
- get_timings()[source]¶
Return a dictionary with timing statistics of the job execution. Returned dictionary contains keys cpu, system and elapsed. The values are corresponding timings, expressed in seconds.
- get_forcefield_params(engine=None)[source]¶
Read all force field data from a forcefield.rkf file into self
filename
– Name of the RKF file that contains ForceField data
- get_exit_condition_message()[source]¶
Tries to get the error message if the driver was stopped by an exit condition. Returns an empty string if no message was provided and the driver was not stopped by an exit condition
- get_pesscan_results(molecules=True)[source]¶
For PESScan jobs, this functions extracts information about the scan coordinates and energies.
- moleculesbool
Whether to return a Molecule at each PES point.
Returns a dictionary, with the following keys:
‘RaveledScanCoords’: a list of str with the names of the scan coordinates: [‘sc_1_1’,’sc_1_2’,’sc_2_1’,’sc_2_2’,…]
‘nRaveledScanCoords’: the length of the previous list
‘RaveledUnits’: a list of str with the units: [‘bohr’, ‘bohr’, ‘radian’, ‘bohr’, …]
‘RaveledPESCoords’: a nested list with the values of the scan coordinates: [[val_1_1_1,val_1_1_2,…],[val_1_2_1,val_1_2_2,…],[val_2_1_1,val_2_1_2,…],[val_2_2_1,val_2_2_2]]
‘ScanCoords’: a nested list: [[‘sc_1_1’,’sc_1_2’],[‘sc_2_1’,’sc_2_2’],…]
‘nScanCoords’: length of the previous list
‘Units’: a nested list with the units: [[‘bohr’, ‘bohr’],[‘radian’, ‘bohr’], …]
‘OrigScanCoords’: a list of str in the newline-separated format stored on the .rkf file: [‘sc_1_1nsc_1_2’,’sc_2_1nsc_2_2’,…]
‘nPESPoints’: int, number of PES points
‘PES’: list of float, the energies at each PES point
‘Converged’: list of bool, whether the geometry optimization at each PES point converged
‘Molecules’: list of Molecule, the structure at each PES point. Only if the property molecules == True
‘HistoryIndices’: list of int, the indices (1-based) in the History section which correspond to the Molecules and PES.
‘ConstrainedAtoms’: set of int, all atom indices (1-based) that were part of any PESScan scan coordinates (other constrained atoms are not included)
‘Properties’: list of dict. The dictionary keys are what can be found on the AMSResults section of the engine .rkf file. These will only be populated if “CalcPropertiesAtPESPoints” is set to Yes when running the PES scan.
- get_neb_results(molecules=True, unit='au')[source]¶
Returns a dictionary with results from a NEB calculation.
- moleculesbool
Whether to include the ‘Molecules’ key in the return result
- unitstr
Energy unit for the Energies, LeftBarrier, RightBarrier, and ReactionEnergy
- Returns: dict
‘nImages’: number of images (excluding end points)
‘nIterations’: number of iterations
‘Energies’: list of energies (including end points)
‘Climbing’: bool, whether climbing image NEB was used
‘LeftBarrier’: float, left reaction barrier
‘RightBarrier’: float, right reaction barrier
‘ReactionEnergy’: float, reaction energy
‘HistoryIndices’: list of int, same length as ‘Energies’, contains indices in the History section
‘Molecules’: list of Molecule (including end points)
- get_irc_results(molecules=True, unit='au')[source]¶
Returns a dictionary with results from an IRC calculation.
- moleculesbool
Whether to include the ‘Molecules’ key in the return result
- unitstr
Energy unit for the Energies, LeftBarrier, RightBarrier
- Returns: dict
‘Energies’: list of energies
‘RelativeEnergies’: list of energies relative to the first point
‘LeftBarrier’: float, left reaction barrier
‘RightBarrier’: float, right reaction barrier
‘Molecules’: list of Molecule (including end points)
‘IRCDirection’: IRC direction
‘Energies’: Energies (in
unit
)‘PathLength’: Path length in angstrom
‘IRCIteration’: list of int
‘IRCGradMax’: list of float
‘IRCGradRms’: list of float
‘ArcLength’: list of float
- get_time_step(history_section='MDHistory')[source]¶
Returns the time step between adjacent frames (NOT the TimeStep in the settings, but Timestep*SamplingFreq) in femtoseconds for MD simulation jobs
- get_velocity_acf(start_fs=0, end_fs=None, every_fs=None, max_dt_fs=None, atom_indices=None, x=True, y=True, z=True, normalize=False)[source]¶
Calculate the velocity autocorrelation function. Works only for trajectories with a constant number of atoms.
- start_fsfloat
Start time in femtoseconds. Defaults to 0 (first frame)
- end_fsfloat
End time in femtoseconds. Defaults to the end of the trajectory.
- max_dt_fsfloat
Maximum correlation time in femtoseconds.
- atom_indices: list of int
Atom indices for which to calculate the velocity autocorrelation function. Defaults to all atoms. The atom indices start with 1.
- xbool
Whether to use the velocities along x
- ybool
Whether to use the velocities along y
- zbool
Whether to use the velocities along z
- normalizebool
Whether to normalize the velocity autocorrelation function so that it is 1 at time 0.
- Returns: 2-tuple (times, C)
times
is a 1D np array with times in femtoseconds.C
is a 1D numpy array with shape (max_dt,) containing the autocorrelation function. If notnormalize
then the unit is angstrom^2/fs^2.
- get_dipole_derivatives_acf(start_fs=0, end_fs=None, every_fs=None, max_dt_fs=None, x=True, y=True, z=True, normalize=False)[source]¶
Calculate the velocity autocorrelation function. Works only for trajectories with a constant number of atoms.
- start_fsfloat
Start time in femtoseconds. Defaults to 0 (first frame)
- end_fsfloat
End time in femtoseconds. Defaults to the end of the trajectory.
- max_dt_fsfloat
Maximum correlation time in femtoseconds.
- xbool
Whether to use the velocities along x
- ybool
Whether to use the velocities along y
- zbool
Whether to use the velocities along z
- normalizebool
Whether to normalize the velocity autocorrelation function so that it is 1 at time 0.
- Returns: 2-tuple (times, C)
times
is a 1D np array with times in femtoseconds.C
is a 1D numpy array with shape (max_dt,) containing the autocorrelation function. If notnormalize
then the unit is angstrom^2/fs^2.
- get_diffusion_coefficient_from_velocity_acf(times=None, acf=None, n_dimensions=3)[source]¶
Diffusion coefficient by integration of the velocity autocorrelation function
If
times
oracf
is None, then a default velocity autocorrelation function will be calculated.- times: 1D numpy array
1D numpy array with the times
- acf: 1D numpy array
1D numpy array with the velocity autocorrelation function in ang^2/fs^2
- n_dimensions: int
Number of dimensions that were used to calculate the autocorrelation function.
- Returns: 2-tuple (times, diffusion_coefficient)
times
are the times in femtoseconds.diffusion_coefficient
is a 1D numpy array with the diffusion coefficients in m^2/s.
- get_power_spectrum(times=None, acf=None, max_dt_fs=None, max_freq=None, number_of_points=None)[source]¶
Calculates a power spectrum from the velocity autocorrelation function.
If
times
oracf
is None, then a default velocity autocorrelation function will be calculated.- times: 1D numpy array of float
The
times
returned byAMSResults.get_velocity_acf()
.- acf: 1D numpy arra of float
The
vacf
returned byAMSResults.get_velocity_acf()
- max_dt_fsfloat
Maximum correlation time in femtoseconds.
- max_freq: float
Maximum frequency (in cm^-1) of the returned power spectrum. Defaults to 5000 cm^-1.
- number_of_points: int
Number of points in the returned power spectrum. Defaults to a number so that the spacing between points is about 1 cm^-1.
- Returns: 2-tuple (frequencies, intensities)
frequencies
: Frequencies in cm^-1 (1D numpy array).intensities
: Intensities (1D numpy array).
- get_ir_spectrum_md(times=None, acf=None, max_dt_fs=None, max_freq=5000, number_of_points=None)[source]¶
Calculates a ir spectrum from the dipole moment autocorrelation function.
If
times
oracf
is None, then a default velocity autocorrelation function will be calculated.- times: 1D numpy array of float
The
times
returned byAMSResults.get_dipole_derivatives_acf()
.- acf: 1D numpy array of float
The
acf
returned byAMSResults.get_dipole_derivatives_acf()
- max_dt_fsfloat
Maximum correlation time in femtoseconds.
- max_freq: float
Maximum frequency (in cm^-1) of the returned power spectrum. Defaults to 5000 cm^-1.
- number_of_points: int
Number of points in the returned power spectrum. Defaults to a number so that the spacing between points is about 1 cm^-1.
- Returns: 2-tuple (frequencies, intensities)
frequencies
: Frequencies in cm^-1 (1D numpy array).intensities
: Intensities (1D numpy array).
- get_green_kubo_viscosity(start_fs=0, end_fs=None, every_fs=None, max_dt_fs=None, xy=True, yz=True, xz=True, pressuretensor=None)[source]¶
Calculates the viscosity using the Green-Kubo relation (integrating the off-diagonal pressure tensor autocorrelation function).
- start_fsfloat
Start time in femtoseconds. Defaults to 0 (first frame)
- end_fsfloat
End time in femtoseconds. Defaults to the end of the trajectory.
- max_dt_fsfloat
Maximum correlation time in femtoseconds.
- xybool
Whether to use the xy off-diagonal elements
- yzbool
Whether to use the yz off-diagonal elements
- xzbool
Whether to use the xz off-diagonal elements
- pressuretensornp.array shape (N,6)
Pressure tensor in atomic units. If not specified, it will be read from the ams.rkf file.
- Returns: 2-tuple (times, C)
times
is a 1D np array with times in femtoseconds.C
is a 1D numpy array with shape (max_dt,) containing the viscosity (in mPa*s) integral. It should converge to the viscosity values as the time increases.
- get_density_along_axis(axis='z', density_type='mass', start_fs=0, end_fs=None, every_fs=None, bin_width=0.1, atom_indices=None)[source]¶
Calculates the density of atoms along a Cartesian coordinate axis.
This only works if the axis is perpendicular to the other two axes. The system must be 3D-periodic and the number of atoms cannot change during the trajectory.
- axisstr
‘x’, ‘y’, or ‘z’
- density_typestr
‘mass’ gives the density in g/cm^3. ‘number’ gives the number density in ang^-3, ‘histogram’ gives the number of atoms per frame (so the number depends on the bin size)
- start_fsfloat
Start time in fs
- end_fsfloat
End time in fs
- every_fsfloat
Use data every every_fs timesteps
- bin_widthfloat
Bin width of the returned coordinates (in angstrom).
- atom_indices: list of int
Indices (starting with 1) for the atoms to use for calculating the density.
- Returns: 2-tuple (coordinates, density)
coordinates
is a 1D array with the coordinates (in angstrom).density
is a 1D array with the densities (in g/cm^3 or ang^-3 depending ondensity_type
).
- get_work_function_results(energy_unit='hartree', dist_unit='bohr')[source]¶
Return a tuple with the results of the work function calculation.
Returns:
coordinate
,planarAverage
,macroscopicAverage
,Efermi
,Vbulk
,Vvacuum
,WF
.coordinate
: 1D array of float.The coordinate perpendicular to the surface. In units of
dist_unit
planarAverage
: 1D array of float.The planar average of the electrostatic potential along the coordinate. In units of
energy_unit
macroscopicAverage
: 1D array of float.The macroscopic average of the electrostatic potential along the coordinate. In units of
energy_unit
Efermi
: float.The Fermi energy. In units of
energy_unit
.Vbulk
: float.The average electrostatic potential in the bulk region of the material. In units of
energy_unit
.Vvacuum
: Tuple[float, float].The average electrostatic potential in the vacuum region far from the surface.
This material is expected to have vacuum regions on both sides. Therefore, the potential is evaluated at the furthest point from the surface in each vacuum region. The first element corresponds to the potential at the furthest point from the surface on the left side, and the second element corresponds to the potential at the furthest point from the surface on the right side. In units of
energy_unit
.Note: If the calculation uses a dipole correction with the QE engine (see QuantumEspresso%Control%tefield parameter in the QE engine), the potential is evaluated right before the x-coordinate where the dipole correction is applied.
WF
: Tuple[float, float].The work function, calculated as WF = Vvacuum - Efermi.
Like Vvacuum, it is evaluated at two points, far from the surface on both sides (left and right). The first and second elements correspond to the work function calculated using the first and second elements of Vvacuum, respectively. In units of
energy_unit
.
Arguments below:
energy_unit
: strUnit of the returned energies
dist_unit
: strUnit of the returned distances
- recreate_molecule()[source]¶
Recreate the input molecule(s) for the corresponding job based on files present in the job folder.
This method is used by
load_external()
. Ifams.rkf
is present in the job folder, extract data from theInputMolecule
andInputMolecule(*)
sections.
- recreate_settings()[source]¶
Recreate the input
Settings
instance for the corresponding job based on files present in the job folder. This method is used byload_external()
.If
ams.rkf
is present in the job folder, extract user input and parse it back to aSettings
instance usingscm.libbase
module. Remove thesystem
branch from that instance.
- ok()[source]¶
Check if the execution of the associated
job
was successful or not. SeeJob.ok
for more information.
- get_errormsg()[source]¶
Tries to get an error message for a associated
job
. This method returnsNone
if the associated job was successful. SeeJob.get_errormsg
for more information.
- property name¶
Return the
job.name
of the job associated with this results instance.
- get_energy_landscape()[source]¶
Returns the energy landscape obtained from a PESExploration job run by the AMS driver. The energy landscape is a set of stationary PES points (local minima and transition states).
The returned object is of the type
AMSResults.EnergyLandscape
and offers convenient access to the states’ energies, geometries as well as the information on which transition states connect which minima.el = results.get_energy_landscape() print(el) for state in el: print(f"Energy = {state.energy}") print(f"Is transition state = {state.isTS}") print("Geometry:", state.molecule) if state.isTS: print(f"Forward barrier: {state.energy - state.reactants.energy}") print(f"Backward barrier: {state.energy - state.products.energy}")
Other functions¶
- hybrid_committee_engine_settings(settings_list)[source]¶
Creates the Settings for an AMS Hybrid engine that takes the average of all subengines as the prediction (for energies and forces).
- settings_list: list of Settings
The Settings (at the top level) for each subengine.
Returns: Settings (at the top level) for a Hybrid engine.