1. Examples¶
1.1. Basic Example¶
The following basic example (examples.ipynb
) shows the general flow of constructing
input. You select the appropriate driver and (usually) an engine. Key
values are directly assigned to the attribute with the same name of the
corresponding block. The get_input_string method produces a
formatted text block representing the text input.
from scm.input_classes import drivers, engines
driver = drivers.AMS()
driver.Task = "GeometryOptimization"
driver.Properties.NormalModes = True
driver.Engine = engines.DFTB()
driver.Engine.Model = "SCC-DFTB"
print(driver.get_input_string())
Properties
NormalModes True
End
Task GeometryOptimization
Engine DFTB
Model SCC-DFTB
EndEngine
1.2. Finding out which keys and blocks are available¶
Besides looking at the documentation pages on the input for the AMS
driver here,
every Block
object has a .blocks
and a .keys
attribute,
which contain all the available blocks and keys for that block. Note
that for .blocks
, it will display the class name first, which is
equal to the attribute name save the leading underscores (to prevent
name clashes). For .keys
, the class name is one of the 7 basic key
types:
print('-------Block-------')
display(drivers.AMS().blocks)
print('-------Keys-------')
display(drivers.AMS().keys)
-------Block-------
(_BondOrders(name='BondOrders', comment="Configures details regarding the calculation/guessing of bond orders. To request the calculation of bond orders, use the 'Properties%BondOrders' key."), _Constraints(name='Constraints', comment='The Constraints block allows geometry optimizations and potential energy surface scans with constraints. The constraints do not have to be satisfied at the start of the calculation.', extra_info='not_in_fragment'), _ElasticTensor(name='ElasticTensor', comment='Options for numerical evaluation of the elastic tensor.'), _Engine(name='Engine', comment='The input for the computational engine. The header of the block determines the type of the engine.', header=True), _EngineAddons(name='EngineAddons', comment='This block configures all the engine add-ons.'), _EngineDebugging(name='EngineDebugging', comment='This block contains some options useful for debugging the computational engines.'), _ExitCondition(name='ExitCondition', comment='If any of the specified exitconditions are met, the AMS driver will exit cleanly.', unique=False), _GCMC(name='GCMC', comment='This block controls the Grand Canonical Monte Carlo (GCMC) task. nn By default, molecules are added at random positions in the simulation box. The initial position is controlled by '), _GeometryOptimization(name='GeometryOptimization', comment='Configures details of the geometry optimization and transition state searches.'), _IEEEExceptions(name='IEEEExceptions', comment='Whether this works or not depends on the way the program is compiled (compiler and settings). Only useful for developers debugging some numerical issue.', hidden=True), _IRC(name='IRC', comment='Configures details of the Intrinsic Reaction Coordinate optimization.'), _Idle(name='Idle', comment='Configures details of the idle task, which does nothing for a specified amount of time.', hidden=True), _LoadSystem(name='LoadSystem', comment='Block that controls reading the chemical system from a KF file instead of the [System] block.', unique=False, header=True, extra_info='not_in_fragment', shared_id='AMSLoadSystem'), _Log(name='Log', comment="Configures the debugging loggers. Syntax: 'Level LoggerName'. Possible Levels: All, Debug, Info, Warning, Error, Fatal."), _MolecularDynamics(name='MolecularDynamics', comment='Configures molecular dynamics (with the velocity-Verlet algorithm) with and without thermostats. This block allows to specify the details of the molecular dynamics calculation.'), _Molecules(name='Molecules', comment='Configures details of the molecular composition analysis enabled by the Properties%Molecules block.'), _NEB(name='NEB', comment='Configures details of the Nudged Elastic Band optimization.'), _NormalModes(name='NormalModes', comment='Configures details of a normal modes calculation.'), _NumericalDifferentiation(name='NumericalDifferentiation', comment='Define options for numerical differentiations, that is the numerical calculation of gradients, Hessian and the stress tensor for periodic systems.'), _NumericalPhonons(name='NumericalPhonons', comment='Configures details of a numerical phonons calculation.'), _PESExploration(name='PESExploration', comment='Configures details of the automated PES exploration methods.'), _PESPointCharacter(name='PESPointCharacter', comment='Options for the characterization of PES points.'), _PESScan(name='PESScan', comment='Configures the details of the potential energy surface scanning task.'), _Print(name='Print', comment='This block controls the printing of additional information to stdout.'), _Properties(name='Properties', comment='Configures which AMS level properties to calculate for SinglePoint calculations or other important geometries (e.g. at the end of an optimization).'), _Raman(name='Raman', comment='Configures details of the Raman or VROA calculation.'), _Replay(name='Replay', comment='Configures the details of the Replay task.'), _Restraints(name='Restraints', comment="The Restraints block allows to add soft constraints to the system. A restraint is a potential energy function (a spring) attached to a certain coordinate, for example, an interatomic distance, with its minimum at the specified optimal value. A restraint is defined using one or two parameters: the ForceConstant and, for some types, the F(Inf) value. The ForceConstant parameter corresponds to second derivative of the restraint potential energy d2V(x)/dx^2 for any x (harmonic restraints) or only at at x=0 (other restraints). Here, x is a deviation from the restraint's optimal value."), _RigidMotions(name='RigidMotions', comment='Specify which rigid motions of the total system are allowed. An external field is not considered part of the system. Normally the automatic option is doing what you want. However this feature can be used as a means of geometry constraint.'), _SCMMatrix(name='SCMMatrix', comment='Technical settings for programs using the AMT matrix system. Currently this is only used by DFTB'), _SteepestDescent(name='SteepestDescent', comment='Configures details of the steepest descent task.', hidden=True), _Symmetry(name='Symmetry', comment='Specifying details about the details of symmetry detection and usage.', shared_id='AMSSymmetry'), _System(name='System', comment='Specification of the chemical system. For some applications more than one system may be present in the input. In this case, all systems except one must have a non-empty string ID specified after the System keyword. The system without an ID is considered the main one.', unique=False, header=True, gui_type='Repeat at least once', shared_id='AMSSystem'), _Thermo(name='Thermo', comment='Options for thermodynamic properties (assuming an ideal gas). The properties are computed for all specified temperatures.'), _TransitionStateSearch(name='TransitionStateSearch', comment='Configures some details of the transition state search.'), _VibrationalAnalysis(name='VibrationalAnalysis', comment='Input data for all vibrational analysis utilities in the AMS driver.'))
-------Keys-------
(StringKey(name='EngineRestart', comment='The path to the file from which to restart the engine.nnShould be a proper engine result file (like adf.rkf, band.rkf etc), or the name of the results directory containing it.', ispath=True), BoolKey(name='FallbackSolveAfterEngineFailure', comment='If the engine fails to Solve, try to re-run the Solve without restarting the engine from the previous results. This generally decreases the engine falure rate. Only relevant certain tasks, such as GeometryOptimization, MolecularDynamics, Replay, IRC.', default=True), StringKey(name='LoadEngine', comment='The path to the file from which to load the engine configuration. Replaces the Engine block.', ispath=True), IntListKey(name='RNGSeed', comment='Initial seed for the (pseudo)random number generator. This should be omitted in most calculations to avoid introducing bias into the results. If this is unset, the generator will be seeded randomly from external sources of entropy. If you want to exactly reproduce an older calculation, set this to the numbers printed in its output.'), MultipleChoiceKey(name='Task', comment='Specify the computational task to perform:nn• Single Point: keep geometry as isn• Geometry Optimization: optimize the geometryn• Transition State: search for the transition staten• IRC: intrinsic reaction coordinaten• PES Scan: scan the potential energy surfacen• NEB: Nudged elastic band for reaction path optimizationn• Vibrational Analysis: perform one of the analysis types selected on the options pagen• Molecular Dynamics: perform MD simulationn• GCMC: Grand Canonical Monte Carlo simulationn• PES Exploration: automated potential energy surface explorationn• Replay: recompute frames from the trajectory of a previously run job', choices=['GCMC', 'GeometryOptimization', 'Idle', 'IRC', 'MolecularDynamics', 'NEB', 'PESExploration', 'PESScan', 'Pipe', 'Replay', 'SinglePoint', 'SteepestDescent', 'TestEngine', 'TestSymmetry', 'TransitionStateSearch', 'VibrationalAnalysis'], hiddenchoices=['Idle', 'SteepestDescent', 'TestEngine', 'TestSymmetry', 'Pipe']), MultipleChoiceKey(name='Test', comment='various technical tests that will be done for the system', hidden=True, default='Symmetry', choices=['Symmetry', 'Bonds', 'SecondOrderBonds', 'AtomTyping', 'SystemMatching']), BoolKey(name='UseSymmetry', comment="Whether to use the system's symmetry in AMS. Symmetry is recognized within a tolerance as given in the Symmetry key.", default=True))
The .comment
attribute provides a description of the entry
drivers.AMS().Engine.comment
'The input for the computational engine. The header of the block determines the type of the engine.'
1.3. Default values¶
Some keys have a default value defined. You can always access the
.default
attribute of any key. When no default is defined, this
attribute will be None
. Upon initialization of the Key
object,
it’s value (.val
attribute) will be set to equal to it’s
.default
attribute. You can always restore the default value with
the .set_to_default
method.
adf = engines.ADF()
print(f"{adf.Basis.Type.default=}")
print(f"{adf.Basis.Type.val=}")
adf.Basis.Type = 'SZ'
print('Value changed to SZ')
print(f"{adf.Basis.Type.default=}")
print(f"{adf.Basis.Type.val=}")
adf.Basis.Type.set_to_default()
print('Value reset to default value')
print(f"{adf.Basis.Type.default=}")
print(f"{adf.Basis.Type.val=}")
adf.Basis.Type.default='DZ'
adf.Basis.Type.val='DZ'
Value changed to SZ
adf.Basis.Type.default='DZ'
adf.Basis.Type.val='SZ'
Value reset to default value
adf.Basis.Type.default='DZ'
adf.Basis.Type.val='DZ'
Note that whether the key is set to the default value or not has no impact on whether it will appear in the text input. Every key that is explicitly set in your input script will be present in the text input:
ams = drivers.AMS()
ams.Engine = engines.ADF()
# set type to the default value
ams.Engine.Basis.Type = 'DZ'
assert ams.Engine.Basis.value_changed
print(ams.get_input_string())
Engine ADF
Basis
Type DZ
End
EndEngine
1.4. Runtime Checks¶
Because of the extensive use of type hints, most mistakes in the syntax can be caught using a type checker like pylance or mypy. PISA will also perform a series or runtime checks, for mistakes that are not possible to catch with a type checker and for users that are not using type checking.
1.4.1. Attribute spelling errors¶
If you mistype the name of an attribute of any Block
, an exception
will be raised that provides with existing attribute names that are
similar to the attribute you typed:
typo_driver = drivers.AMS()
typo_driver.Tassk = "GeometryOptimization"
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[6], line 2
1 typo_driver = drivers.AMS()
----> 2 typo_driver.Tassk = "GeometryOptimization"
File ~/src/svn_amshome/scripting/scm/pisa/block.py:372, in Block.__setattr__(self, _Block__name, _Block__value)
370 raise Exception(f"Attribute '{__name}' not found, did you mean '{proper_case_name}'?")
371 if not hasattr(self, __name):
--> 372 super().__setattr__(__name, __value)
373 return
374 else:
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:124, in Entry.__setattr__(self, _Entry__name, _Entry__value)
122 elif len(close_matches) > 1:
123 msg += f" Did you possibly mean to access any of [{', '.join(close_matches)}]?"
--> 124 raise AttributeError(msg)
125 return super().__setattr__(__name, __value)
AttributeError: Object of type <class 'scm.input_classes.drivers.ams.AMS'> has no attribute called 'Tassk'. Did you possibly mean to access any of [Task, Test, ElasticTensor]?
1.4.2. Invalid choice for a multiple-choice key¶
When setting a multiple-choice key to a value that is not a valid choice, an exception will be raised that includes the list of valid choices:
adf = engines.ADF()
adf.Basis.Type = 'TZQP'
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:257, in MultipleChoiceKey._convert_val(self, val)
256 try:
--> 257 idx = lower_choices.index(val.lower())
258 return total_choices[idx]
ValueError: 'tzqp' is not in list
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
Cell In[7], line 2
1 adf = engines.ADF()
----> 2 adf.Basis.Type = 'TZQP'
File ~/src/svn_amshome/scripting/scm/pisa/block.py:404, in Block.__setattr__(self, _Block__name, _Block__value)
402 # allow syntax like AMS().Task = 'SinglePoint'
403 elif isinstance(attr, Key) and not isinstance(__value, Key):
--> 404 attr.val = __value
405 else:
406 super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:125, in Entry.__setattr__(self, _Entry__name, _Entry__value)
123 msg += f" Did you possibly mean to access any of [{', '.join(close_matches)}]?"
124 raise AttributeError(msg)
--> 125 return super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:58, in Key.val(self, value)
55 @val.setter
56 def val(self, value: Any) -> None:
57 self._value_changed = True
---> 58 self._val = self._convert_val(value)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:261, in MultipleChoiceKey._convert_val(self, val)
259 except ValueError:
260 choice_list = "\n".join(total_choices)
--> 261 raise ValueError(
262 f"Can not set value to {val}, not present in choices: {choice_list}. \n"
263 f"Did you possibly mean any of {difflib.get_close_matches(val, total_choices)}?"
264 )
ValueError: Can not set value to TZQP, not present in choices: SZ
DZ
DZP
TZP
TZ2P
QZ4P
TZ2P+
TZ2P-J
QZ4P-J
mTZ2P
ZORA/SZ
ZORA/DZ
ZORA/DZP
ZORA/TZP
ZORA/TZ2P
ZORA/QZ4P
ZORA/TZ2P+
ZORA/TZ2P-J
ZORA/QZ4P-J
ZORA/mTZ2P
ZORA/jcpl
AUG/ASZ
AUG/ADZ
AUG/ADZP
AUG/ATZP
AUG/ATZ2P
ET/ET-pVQZ
ET/ET-QZ+5P
ET/ET-QZ3P
ET/ET-QZ3P-1DIFFUSE
ET/ET-QZ3P-2DIFFUSE
ET/ET-QZ3P-3DIFFUSE
Corr/TZ3P
Corr/QZ6P
Corr/ATZ3P
Corr/AQZ6P
Corr/dATZ3P
Corr/dAQZ6P
POLTDDFT/DZ
POLTDDFT/DZP
POLTDDFT/TZP
TZ2P+
ZORA/SZ
ZORA/DZ
ZORA/DZP
ZORA/TZP
ZORA/TZ2P
ZORA/QZ4P
ZORA/TZ2P+
ZORA/TZ2P-J
ZORA/QZ4P-J
ZORA/mTZ2P
ZORA/jcpl
ET/ET-QZ+5P
Corr/dATZ3P
Corr/dAQZ6P.
Did you possibly mean any of ['TZP', 'TZ2P', 'mTZ2P']?
1.4.3. Invalid type for a key value¶
When providing a key with a value that is of the wrong type or can not reasonably be coerced to the correct type, an exception will be raised:
adf = engines.ADF()
adf.A1Fit = 'Wrong'
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[8], line 2
1 adf = engines.ADF()
----> 2 adf.A1Fit = 'Wrong'
File ~/src/svn_amshome/scripting/scm/pisa/block.py:404, in Block.__setattr__(self, _Block__name, _Block__value)
402 # allow syntax like AMS().Task = 'SinglePoint'
403 elif isinstance(attr, Key) and not isinstance(__value, Key):
--> 404 attr.val = __value
405 else:
406 super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:125, in Entry.__setattr__(self, _Entry__name, _Entry__value)
123 msg += f" Did you possibly mean to access any of [{', '.join(close_matches)}]?"
124 raise AttributeError(msg)
--> 125 return super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:58, in Key.val(self, value)
55 @val.setter
56 def val(self, value: Any) -> None:
57 self._value_changed = True
---> 58 self._val = self._convert_val(value)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:135, in FloatKey._convert_val(self, val)
125 def _convert_val(self, val: float) -> float:
126 """Will accept anything that can be converted to a python float, but raises errors on values of
127 type :class:`bool`, since the type hinter considers them a valid subclass of :class:`float`, but passing
128 booleans as floats is never intended
(...)
133 :rtype: float
134 """
--> 135 return float(self._check_for_boolean(val))
ValueError: could not convert string to float: 'Wrong'
Note that floating point values with a fractional component cannot be set to integer keys:
adf.VectorLength = 1
adf.VectorLength = 2.0 # valid
adf.VectorLength = 3.5 # invalid
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In [8], line 3
1 adf.VectorLength = 1
2 adf.VectorLength = 2.0 # valid
----> 3 adf.VectorLength = 3.5
File ~/src/svn_amshome/scripting/scm/pisa/block.py:388, in Block.__setattr__(self, _Block__name, _Block__value)
386 # allow syntax like AMS().Task = 'SinglePoint'
387 elif isinstance(attr, Key) and not isinstance(__value, Key):
--> 388 attr.val = __value
389 else:
390 super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:125, in Entry.__setattr__(self, _Entry__name, _Entry__value)
123 msg += f" Did you possibly mean to access any of [{', '.join(close_matches)}]?"
124 raise AttributeError(msg)
--> 125 return super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:57, in Key.val(self, value)
54 @val.setter
55 def val(self, value: Any) -> None:
56 self._value_changed = True
---> 57 self._val = self._convert_val(value)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:191, in IntKey._convert_val(self, val)
183 def _convert_val(self, val: int) -> int:
184 """Accepts anything that can be converted to an integer, except boolean values and floats with a fractional component.
185
186 :param val: value
(...)
189 :rtype: int
190 """
--> 191 return int(self._check_for_float(self._check_for_boolean(val)))
File ~/src/svn_amshome/scripting/scm/pisa/key.py:123, in Key._check_for_float(self, val)
121 def _check_for_float(self, val: T) -> T:
122 if (isinstance(val, float) and not val.is_integer()) or (isinstance(val, str) and not float(val).is_integer()):
--> 123 raise ValueError(f"Will not accept floating point value {val} for {self.__class__.__name__} {self}")
124 return val
ValueError: Will not accept floating point value 3.5 for IntKey <Key: 'VectorLength 2'>
1.4.4. Boolean key values¶
Boolean values represent a bit of a problem in Python. For historical
reasons, bool
is a direct subclass of int
. This makes it
troublesome to provide type hints for IntKey
and FloatKey
attributes that do not accept booleans. Hence there is an explicit check
for these keys to prevent accidentally passing booleans as numbers:
from scm.pisa.key import FloatKey, IntKey
# instances of bool are also instances of int
print(f"{isinstance(True, int)=}")
# Coercing boolean values to floats or int will succeed
print(f"{int(True)=}")
print(f"{float(True)=}")
# To prevent users mistaking a FloatKey for a BoolKey, an explicit runtime check if performed
adf = engines.ADF()
adf.VectorLength = 1 # this is allowed
adf.VectorLength = True # this is not allowed, even though it would evaluate to the same integer
isinstance(True, int)=True
int(True)=1
float(True)=1.0
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[9], line 11
9 adf = engines.ADF()
10 adf.VectorLength = 1 # this is allowed
---> 11 adf.VectorLength = True # this is not allowed, even though it would evaluate to the same integer
File ~/src/svn_amshome/scripting/scm/pisa/block.py:404, in Block.__setattr__(self, _Block__name, _Block__value)
402 # allow syntax like AMS().Task = 'SinglePoint'
403 elif isinstance(attr, Key) and not isinstance(__value, Key):
--> 404 attr.val = __value
405 else:
406 super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:125, in Entry.__setattr__(self, _Entry__name, _Entry__value)
123 msg += f" Did you possibly mean to access any of [{', '.join(close_matches)}]?"
124 raise AttributeError(msg)
--> 125 return super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:58, in Key.val(self, value)
55 @val.setter
56 def val(self, value: Any) -> None:
57 self._value_changed = True
---> 58 self._val = self._convert_val(value)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:186, in IntKey._convert_val(self, val)
178 def _convert_val(self, val: int) -> int:
179 """Accepts anything that can be converted to an integer, except boolean values.
180
181 :param val: value
(...)
184 :rtype: int
185 """
--> 186 return int(self._check_for_boolean(val))
File ~/src/svn_amshome/scripting/scm/pisa/key.py:118, in Key._check_for_boolean(self, val)
116 def _check_for_boolean(self, val: T) -> T:
117 if isinstance(val, bool):
--> 118 raise TypeError(f"Will not accept boolean value {val} for {self.__class__.__name__} {self}")
119 return val
TypeError: Will not accept boolean value True for IntKey <Key: 'VectorLength 1'>
Similarly, since bool(obj)
will work in Python on objects of any
type, so for BoolKey
attributes, only the booleans True
and
False
are accepted, plus any case variation of some strings for
historic (fortran-related) reasons:
adf = engines.ADF()
# this is all accepted
adf.AccurateGradients = True
adf.AccurateGradients = False
adf.AccurateGradients = 'YeS'
adf.AccurateGradients = 'nO'
adf.AccurateGradients = 'T'
adf.AccurateGradients = 'f'
# this will be rejected
adf.AccurateGradients = 1
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[10], line 12
9 adf.AccurateGradients = 'f'
11 # this will be rejected
---> 12 adf.AccurateGradients = 1
File ~/src/svn_amshome/scripting/scm/pisa/block.py:404, in Block.__setattr__(self, _Block__name, _Block__value)
402 # allow syntax like AMS().Task = 'SinglePoint'
403 elif isinstance(attr, Key) and not isinstance(__value, Key):
--> 404 attr.val = __value
405 else:
406 super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:125, in Entry.__setattr__(self, _Entry__name, _Entry__value)
123 msg += f" Did you possibly mean to access any of [{', '.join(close_matches)}]?"
124 raise AttributeError(msg)
--> 125 return super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:58, in Key.val(self, value)
55 @val.setter
56 def val(self, value: Any) -> None:
57 self._value_changed = True
---> 58 self._val = self._convert_val(value)
File ~/src/svn_amshome/scripting/scm/pisa/key.py:166, in BoolKey._convert_val(self, val)
164 return False
165 else:
--> 166 raise ValueError(
167 f"{str_val} not in accepted options for True: {accepted_true} or " f"False: {accepted_false}"
168 )
ValueError: 1 not in accepted options for True: ['true', 'yes', 't'] or False: ['false', 'no', 'f']
1.4.5. Overwriting a Block
attribute that is not an EngineBlock
, FreeBlock
or an InputBlock
¶
Except for the block types mentioned in the title, block attributes are not meant to be overwritten:
from scm.pisa.block import EngineBlock
ams = drivers.AMS()
# The AMS.Engine attribute is of type EngineBlock
print(f"{isinstance(ams.Engine, EngineBlock)=}")
# And thus you are allowed to override it with an actual Engine
ams.Engine = engines.ADF()
# But you are not allowed to overwrite it with anything else
ams.Engine = 'Foo'
isinstance(ams.Engine, EngineBlock)=True
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[11], line 9
7 ams.Engine = engines.ADF()
8 # But you are not allowed to overwrite it with anything else
----> 9 ams.Engine = 'Foo'
File ~/src/svn_amshome/scripting/scm/pisa/block.py:382, in Block.__setattr__(self, _Block__name, _Block__value)
380 super().__setattr__(__name, __value)
381 else:
--> 382 raise TypeError(f"Expect type 'EngineBlock', received type {type(__value)}: {repr(__value)}")
383 elif isinstance(attr, FreeBlock):
384 is_iterable = False
TypeError: Expect type 'EngineBlock', received type <class 'str'>: 'Foo'
# Allowed to override free blocks with an iterable of strings
ams.Log = ['Freeblock line 1', 'FreeBlock line 2']
print(ams.Log.get_input_string())
# Or with a single multiline string
ams.Log = '''\
Freeblock line 1
FreeBlock line 2
'''
print(ams.Log.get_input_string())
# But not with anything else
ams.Log = True
Log
Freeblock line 1
FreeBlock line 2
End
Log
Freeblock line 1
FreeBlock line 2
End
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[12], line 11
9 print(ams.Log.get_input_string())
10 # But not with anything else
---> 11 ams.Log = True
File ~/src/svn_amshome/scripting/scm/pisa/block.py:395, in Block.__setattr__(self, _Block__name, _Block__value)
393 attr._multiline_string = "\n".join(str(line) for line in __value) + "\n"
394 else:
--> 395 raise TypeError(
396 f"Expected a string or an iterable of strings to assign to FreeBlock attribute {attr}. "
397 f"Received {__value}"
398 )
399 attr._value_changed = True
400 elif isinstance(attr, Block):
TypeError: Expected a string or an iterable of strings to assign to FreeBlock attribute Log
Freeblock line 1
FreeBlock line 2
End
. Received True
# Not allowed to override general Block attributes
ams.System = 'foo'
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[13], line 2
1 # Not allowed to override general Block attributes
----> 2 ams.System = 'foo'
File ~/src/svn_amshome/scripting/scm/pisa/block.py:401, in Block.__setattr__(self, _Block__name, _Block__value)
399 attr._value_changed = True
400 elif isinstance(attr, Block):
--> 401 raise AttributeError(f"Not allowed to overwrite block attribute {attr}")
402 # allow syntax like AMS().Task = 'SinglePoint'
403 elif isinstance(attr, Key) and not isinstance(__value, Key):
AttributeError: Not allowed to overwrite block attribute System
End
1.4.6. Path keys¶
When the .ispath
attribute of a StringKey
is True
, any
strings passed to it will be checked to see if they represent an
existing relative path. If so, the string will be replaced by a string
representing the absolute path and the user will be warned of this. The
reason for this is that often a path is entered relative to the location
of the PLAMS script, but when the job is actually running the working
directory will be different and the path can no longer be resolved.
! touch foo.rkf
ams = drivers.AMS()
# this will display a warning
ams.EngineRestart = 'foo.rkf'
print(f"{ams.get_input_string()=}")
# non existing paths will be left alone
ams.EngineRestart = 'bar.rkf'
print(f"{ams.get_input_string()=}")
! rm foo.rkf
ams.get_input_string()='EngineRestart foo.rkfn' ams.get_input_string()='EngineRestart bar.rkfn'
1.4.7. Repeated Entry indexing¶
Some keys/blocks have their .unique
attributes set to False
,
meaning multiple occurrences of the entry are allowed in the input. This
can be accomplished using the Python index notation [<index>]
. This
indexing is not allowed on unique entries.
ams = drivers.AMS()
print(f"{ams.EngineRestart.unique=}")
ams.EngineRestart[0] = 'bar.rkf'
ams.EngineRestart.unique=True
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[15], line 3
1 ams = drivers.AMS()
2 print(f"{ams.EngineRestart.unique=}")
----> 3 ams.EngineRestart[0] = 'bar.rkf'
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:27, in _check_unique.<locals>.wrapper(obj, *args, **kwargs)
25 assert isinstance(obj, Entry)
26 if obj.unique:
---> 27 raise ValueError(f"Calling method {meth.__name__} not allowed on unique entry {obj}")
28 return meth(obj, *args, **kwargs)
ValueError: Calling method __setitem__ not allowed on unique entry <Key: 'EngineRestart None'>
hybrid = engines.Hybrid()
hybrid.Engine[0] = engines.ADF()
hybrid.Engine[1] = engines.BAND()
print(hybrid.get_input_string())
Engine Hybrid
Engine ADF
EndEngine
Engine BAND
EndEngine
EndEngine
The repeated entries need to be created in order, with an indexing starting at zero. If you accidentally skip an index, an exception will be raised that will point you to the index you have to create:
hybrid.Engine[3] = engines.DFTB()
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
File ~/src/svn_amshome/scripting/scm/pisa/block.py:425, in Block.__setitem__(self, index, value)
424 try:
--> 425 self._entry_list[index] = value
426 except IndexError:
IndexError: list assignment index out of range
During handling of the above exception, another exception occurred:
IndexError Traceback (most recent call last)
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:193, in Entry.__getitem__(self, index)
192 try:
--> 193 return self._entry_list[index]
194 except IndexError:
IndexError: list index out of range
During handling of the above exception, another exception occurred:
IndexError Traceback (most recent call last)
Cell In[17], line 1
----> 1 hybrid.Engine[3] = engines.DFTB()
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:28, in _check_unique.<locals>.wrapper(obj, *args, **kwargs)
26 if obj.unique:
27 raise ValueError(f"Calling method {meth.__name__} not allowed on unique entry {obj}")
---> 28 return meth(obj, *args, **kwargs)
File ~/src/svn_amshome/scripting/scm/pisa/block.py:430, in Block.__setitem__(self, index, value)
428 self._entry_list = [*self._entry_list, value]
429 else:
--> 430 self[index]
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:28, in _check_unique.<locals>.wrapper(obj, *args, **kwargs)
26 if obj.unique:
27 raise ValueError(f"Calling method {meth.__name__} not allowed on unique entry {obj}")
---> 28 return meth(obj, *args, **kwargs)
File ~/src/svn_amshome/scripting/scm/pisa/block.py:552, in EngineBlock.__getitem__(self, index)
550 @_check_unique
551 def __getitem__(self, index: int) -> Any:
--> 552 return super().__getitem__(index)
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:28, in _check_unique.<locals>.wrapper(obj, *args, **kwargs)
26 if obj.unique:
27 raise ValueError(f"Calling method {meth.__name__} not allowed on unique entry {obj}")
---> 28 return meth(obj, *args, **kwargs)
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:222, in Entry.__getitem__(self, index)
220 return self.__getitem__(index)
221 else:
--> 222 raise IndexError(
223 f"Tried to access {self.name}[{index}], but {self.name}[{len(self)}] does not exist. "
224 f"First create {self.name}[{len(self)}]"
225 )
IndexError: Tried to access Engine[3], but Engine[2] does not exist. First create Engine[2]
1.4.8. Setting of block headers¶
Some blocks allow a header to be set, which will show up next to the
entry name in the text input. You can check if a header is allowed via
the .allow_header
property. The header can simply be set by
assigning a string to the .header
attribute.
ams = drivers.AMS()
print(f"{ams.System.allow_header=}")
ams.System.header = 'MyHeader'
ams.System.Symmetry = 'AUTO'
print(ams.get_input_string())
# An exception will be raised when setting the header blocks that don't allow headers
ams.Symmetry.header = 'MyHeader'
ams.System.allow_header=True
System MyHeader
Symmetry AUTO
End
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[18], line 9
6 print(ams.get_input_string())
8 # An exception will be raised when setting the header blocks that don't allow headers
----> 9 ams.Symmetry.header = 'MyHeader'
File ~/src/svn_amshome/scripting/scm/pisa/block.py:406, in Block.__setattr__(self, _Block__name, _Block__value)
404 attr.val = __value
405 else:
--> 406 super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/entry.py:125, in Entry.__setattr__(self, _Entry__name, _Entry__value)
123 msg += f" Did you possibly mean to access any of [{', '.join(close_matches)}]?"
124 raise AttributeError(msg)
--> 125 return super().__setattr__(__name, __value)
File ~/src/svn_amshome/scripting/scm/pisa/block.py:76, in Block.header(self, val)
74 self._header = str(val)
75 else:
---> 76 raise ValueError(f"Block {self.name} does not allow setting of header")
ValueError: Block Symmetry does not allow setting of header
1.5. Integration with PLAMS¶
1.5.1. Passing driver object to AMSJob¶
There are multiple ways to pass your driver object to an instance of
AMSJob
.
The first way is to create an empty Settings
object, and insert your
driver object under the .input
attribute. Then simply pass the
setting object to the job as usual. This allows you to also pass extra
configuration in the settings object:
from scm.plams import AMSJob, Settings, Molecule, Atom
settings = Settings()
driver = drivers.AMS()
driver.Task = "GeometryOptimization"
driver.Properties.NormalModes = True
driver.Engine = engines.DFTB()
driver.Engine.Model = "SCC-DFTB"
settings.input = driver
molecule = Molecule()
molecule.add_atom(Atom(symbol='O', coords=(0,0,0)))
molecule.add_atom(Atom(symbol='H', coords=(1,0,0)))
molecule.add_atom(Atom(symbol='H', coords=(0,1,0)))
job = AMSJob(molecule=molecule, settings=settings, name='water_optimization')
print(job.get_input())
Properties
NormalModes True
End
Task GeometryOptimization
Engine DFTB
Model SCC-DFTB
EndEngine
System
Atoms
O 0.0000000000 0.0000000000 0.0000000000
H 1.0000000000 0.0000000000 0.0000000000
H 0.0000000000 1.0000000000 0.0000000000
End
End
Alternatively you can also pass your driver object as the settings
argument itself. The job object will then create an empty settings
object and insert the driver object under the .input
attribute:
job = AMSJob(molecule=molecule, settings=driver, name='water_optimization')
print(f"{job.settings.input=}")
job.settings.input=AMS(name='AMS')
Finally you can also create the job object first and start assigning to
the .settings.input
attribute, leading to a slightly more verbose
syntax:
job = AMSJob(molecule=molecule, name='water_optimization')
job.settings.input = drivers.AMS()
job.settings.input.Engine = engines.ADF()
print(job.get_input())
# etc
Engine ADF
EndEngine
System
Atoms
O 0.0000000000 0.0000000000 0.0000000000
H 1.0000000000 0.0000000000 0.0000000000
H 0.0000000000 1.0000000000 0.0000000000
End
End
1.5.2. Conversion to and from PLAMS Settings¶
For backwards compatibility and convenience, the Block
objects have
a to_settings and from_settings method, which both work by
passing text input to the InputParser
class. The to_settings
method is quite trustworthy, since it generates a more loosely defined
object from a more strictly defined object. The from_settings
object
works with basic settings, but is not extensively tested with all
possible forms of the settings object.
See the examples below for simple conversions:
from scm.input_classes import AMS, DFTB
from scm.plams.interfaces.adfsuite.ams import AMSJob
from scm.plams.core.settings import Settings
ams = AMS()
ams.Task = 'SinglePoint'
ams.Engine = DFTB()
s = Settings()
s.input = ams.to_settings()
job = AMSJob(settings=s)
print(job.get_input())
task SinglePoint
Engine dftb
EndEngine
from scm.input_classes import AMS, DFTB
from scm.plams.interfaces.adfsuite.ams import AMSJob
from scm.plams.core.settings import Settings
settings = Settings()
settings.input
settings.input.ams.Task = 'GeometryOptimization'
settings.input.ams.Properties.NormalModes = 'Yes'
settings.input.DFTB.Model = 'SCC-DFTB'
ams = AMS.from_settings(settings)
print(ams.get_input_string())
# this is supported, but fragile:
dftb = DFTB.from_settings(settings)
print(dftb.get_input_string())
# this is safer:
ams = AMS.from_settings(settings)
dftb = ams.engine
Properties
NormalModes True
End
Task GeometryOptimization
Engine DFTB
Model SCC-DFTB
EndEngine
/home/vdkolk/src/svn_amshome/scripting/scm/pisa/block.py:218: UserWarning: Could not generate <class 'scm.input_classes.engines.dftb.DFTB'> from textinput:
Properties
NormalModes Yes
End
Task GeometryOptimization
Engine DFTB
Model SCC-DFTB
EndEngine
Retrying (fragile!) with subset:
Model SCC-DFTB
warnings.warn(msg)
Engine DFTB
Model SCC-DFTB
EndEngine
1.6. Repeated blocks and headers¶
The following is a more complex example using repeated blocks and
headers. For blocks that allow headers (Engine blocks allow headers by
default), you can simply set a string value for the .header
attribute.
For repeating blocks, you can use the Python index notation with square brackets to create and access new instances of the repeated block on the fly. This requires you to access the instances in order, starting at 0.
For repeating engine blocks, like in the following example, it is beneficial to first create the engine block instance and assigning values to the relevant attributes, before assigning the engine block as a whole using the index notation. This ensures you can use the full benefits of the typing system, since it can not dynamically infer the different engine types.
driver = drivers.AMS()
driver.Task = "Replay"
driver.Replay.File = "/foo/bar/ams.rkf"
driver.Engine = engines.Hybrid()
driver.Engine.Energy.DynamicFactors = "UseLowestEnergy"
# repeated block, start indexing at 0
driver.Engine.Energy.Term[0].Region = "*"
driver.Engine.Energy.Term[0].EngineID = "Singlet"
driver.Engine.Energy.Term[1].Region = "*"
driver.Engine.Energy.Term[1].EngineID = "Triplet"
# create the engine object first, to benefit from type hinting
singlet_engine = engines.ADF()
singlet_engine.header = "Singlet"
singlet_engine.Unrestricted = "No"
singlet_engine.XC.GGA = "PBE"
singlet_engine.Basis.Type = "DZP"
singlet_engine.SCF.Iterations = 100
# do not forget to assign it to the repeated engine block
driver.Engine.Engine[0] = singlet_engine
triplet_engine = engines.ADF()
triplet_engine.header = "Triplet"
triplet_engine.Unrestricted = "Yes"
triplet_engine.SpinPolarization = 2
triplet_engine.XC.GGA = "PBE"
triplet_engine.Basis.Type = "DZP"
triplet_engine.SCF.Iterations = 100
driver.Engine.Engine[1] = triplet_engine
print(driver.get_input_string())
Replay
File /foo/bar/ams.rkf
End
Task Replay
Engine Hybrid
Energy
DynamicFactors UseLowestEnergy
Term
EngineID Singlet
Region *
End
Term
EngineID Triplet
Region *
End
End
Engine ADF Singlet
Basis
Type DZP
End
SCF
Iterations 100
End
Unrestricted False
XC
GGA PBE
End
EndEngine
Engine ADF Triplet
Basis
Type DZP
End
SCF
Iterations 100
End
SpinPolarization 2.0
Unrestricted True
XC
GGA PBE
End
EndEngine
EndEngine
1.7. Free blocks¶
Free blocks can be assigned with a either a multiline string or a Sequence/Iterable of strings:
driver = drivers.AMS()
adf = engines.ADF()
adf.RISM = """\
H 0.0 0.0 0.0
O 1.0 0.0 0.0
O 0.0 1.0 0.0"""
driver.Engine = adf
print(driver.get_input_string())
adf.RISM = (
'H 0.0 1.0 0.0',
'O 0.0 1.0 0.0',
'O 1.0 1.0 1.0',
)
print(driver.get_input_string())
Engine ADF
RISM
H 0.0 0.0 0.0
O 1.0 0.0 0.0
O 0.0 1.0 0.0
End
EndEngine
Engine ADF
RISM
H 0.0 1.0 0.0
O 0.0 1.0 0.0
O 1.0 1.0 1.0
End
EndEngine
1.8. Instantiating block instances from existing text input¶
To instantiate a block instance from existing text, you can use the from_text class method of the appropriate driver or engine class. Note that for the Engine attribute of such dynamically generated instances, some type hinters might generate false positives.
text_input = """\
Task SinglePoint
Engine BAND
Basis
Type DZP
End
DOS
CalcPDOS True
End
HubbardU
Enabled True
LValue 2 -1
UValue 0.6 0.0
End
KSpace
Quality Basic
End
NumericalQuality Normal
Unrestricted True
XC
gga BP86
End
EndEngine
"""
driver = drivers.AMS.from_text(text_input)
driver.Engine.Basis.Type = 'SZ'
print(driver.get_input_string())
Task SinglePoint
Engine BAND
Basis
Type SZ
End
DOS
CalcPDOS True
End
HubbardU
Enabled True
LValue 2 -1
UValue 0.6 0.0
End
KSpace
Quality Basic
End
NumericalQuality Normal
Unrestricted True
XC
gga BP86
End
EndEngine