4.1.2. AMS driver¶
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 AMS in the corresponding part of the documentation.
4.1.2.1. Preparing input¶
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
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.
4.1.2.2. 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).
4.1.2.3. 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
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 (first vector aligned with X axis, second vector 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, much 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)
It is worth noting here that both methods mentioned above can be mixed and the contents of Settings
take precedence over the Molecule
.
In other words, if myjob.settings.input.ams.system
already contains atoms
, lattice
, or charge
entries, the corresponding information from myjob.molecule
is ignored.
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)
In such a case the contents of myjob.molecule
attribute are completely ignored and the geoemtry from the given file is used.
Currently only the extended XYZ format for details) is supported.
4.1.2.4. 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
.-
check
()[source]¶ Check if
termination status
variable fromGeneral
section of main KF file equalsNORMAL TERMINATION
.
-
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:
AMS_JOBNAME=jobname AMS_RESULTSDIR=. $ADFBIN/ams [-n nproc] <jobname.in [>jobname.out]
-n
flag is added ifsettings.runscript.nproc
exists.[>jobname.out]
is used based onsettings.runscript.stdout_redirect
.
-
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.
-
4.1.2.5. 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.
-
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.
-
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_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_input_molecule
()[source]¶ Return a
Molecule
instance with initial coordinates.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 final coordinates.All data used by this method is taken from
ams.rkf
file. Themolecule
attribute of the corresponding job is ignored.
-
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 you wish to read. To access a file called
something.rkf
you need to call this function withfile='something
‘.All data used by this method is taken from the chosen
.rkf
file. Themolecule
attribute of the corresponding job is ignored.
-
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 you wish to read. To access a file called
something.rkf
you need to call this function withfile='something
‘.
-
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 you wish to read. To access a file called
something.rkf
you need to call this function withfile='something
‘.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.
-
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 you wish to read. To access a file called
something.rkf
you need to call this function withfile='something
‘.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.
-
recreate_molecule
()[source]¶ Recreate the input molecule for the corresponding job based on files present in the job folder. This method is used by
load_external()
.If
ams.rkf
is present in the job folder, extract data from theInputMolecule
section.
-
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.input_parser
module. Remove thesystem
branch from that instance.
-
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).
-