{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Examples\n", "\n", "## Basic Example\n", "\n", "The following basic example 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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from scm.input_classes import drivers, engines\n", "\n", "driver = drivers.AMS()\n", "driver.Task = \"GeometryOptimization\"\n", "driver.Properties.NormalModes = True\n", "driver.Engine = engines.DFTB()\n", "driver.Engine.Model = \"SCC-DFTB\"\n", "\n", "print(driver.get_input_string())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Finding out which keys and blocks are available\n", "\n", "Besides looking at the documentation pages on the input for the AMS driver `here <../AMS/keywords.html#summary-of-all-keywords`__, 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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"-------Block-------\")\n", "display(drivers.AMS().blocks)\n", "print(\"-------Keys-------\")\n", "display(drivers.AMS().keys)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `.comment` attribute provides a description of the entry" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "drivers.AMS().Engine.comment" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Default values\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "adf = engines.ADF()\n", "print(f\"{adf.Basis.Type.default=}\")\n", "print(f\"{adf.Basis.Type.val=}\")\n", "adf.Basis.Type = \"SZ\"\n", "print(\"Value changed to SZ\")\n", "print(f\"{adf.Basis.Type.default=}\")\n", "print(f\"{adf.Basis.Type.val=}\")\n", "adf.Basis.Type.set_to_default()\n", "print(\"Value reset to default value\")\n", "print(f\"{adf.Basis.Type.default=}\")\n", "print(f\"{adf.Basis.Type.val=}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ams = drivers.AMS()\n", "ams.Engine = engines.ADF()\n", "# set type to the default value\n", "ams.Engine.Basis.Type = \"DZ\"\n", "assert ams.Engine.Basis.value_changed\n", "print(ams.get_input_string())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Runtime Checks\n", "\n", "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.\n", "\n", "### Attribute spelling errors\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "typo_driver = drivers.AMS()\n", "typo_driver.Tassk = \"GeometryOptimization\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Invalid choice for a multiple-choice key\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "adf = engines.ADF()\n", "adf.Basis.Type = \"TZQP\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Invalid type for a key value\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "adf = engines.ADF()\n", "adf.A1Fit = \"Wrong\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that floating point values with a fractional component cannot be set to integer keys:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "adf.VectorLength = 1\n", "adf.VectorLength = 2.0 # valid\n", "adf.VectorLength = 3.5 # invalid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Boolean key values\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from scm.pisa.key import FloatKey, IntKey\n", "\n", "# instances of bool are also instances of int\n", "print(f\"{isinstance(True, int)=}\")\n", "# Coercing boolean values to floats or int will succeed\n", "print(f\"{int(True)=}\")\n", "print(f\"{float(True)=}\")\n", "# To prevent users mistaking a FloatKey for a BoolKey, an explicit runtime check if performed\n", "adf = engines.ADF()\n", "adf.VectorLength = 1 # this is allowed\n", "adf.VectorLength = True # this is not allowed, even though it would evaluate to the same integer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "adf = engines.ADF()\n", "\n", "# this is all accepted\n", "adf.AccurateGradients = True\n", "adf.AccurateGradients = False\n", "adf.AccurateGradients = \"YeS\"\n", "adf.AccurateGradients = \"nO\"\n", "adf.AccurateGradients = \"T\"\n", "adf.AccurateGradients = \"f\"\n", "\n", "# this will be rejected\n", "adf.AccurateGradients = 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Overwriting a `Block` attribute that is not an `EngineBlock`, `FreeBlock` or an `InputBlock`\n", "\n", "Except for the block types mentioned in the title, block attributes are not meant to be overwritten:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from scm.pisa.block import EngineBlock\n", "\n", "ams = drivers.AMS()\n", "# The AMS.Engine attribute is of type EngineBlock\n", "print(f\"{isinstance(ams.Engine, EngineBlock)=}\")\n", "# And thus you are allowed to override it with an actual Engine\n", "ams.Engine = engines.ADF()\n", "# But you are not allowed to overwrite it with anything else\n", "ams.Engine = \"Foo\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Allowed to override free blocks with an iterable of strings\n", "ams.Log = [\"Freeblock line 1\", \"FreeBlock line 2\"]\n", "print(ams.Log.get_input_string())\n", "# Or with a single multiline string\n", "ams.Log = \"\"\"\\\n", "Freeblock line 1\n", "FreeBlock line 2\n", "\"\"\"\n", "print(ams.Log.get_input_string())\n", "# But not with anything else\n", "ams.Log = True" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Not allowed to override general Block attributes\n", "ams.System = \"foo\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Path keys\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "! touch foo.rkf\n", "ams = drivers.AMS()\n", "# this will display a warning\n", "ams.EngineRestart = \"foo.rkf\"\n", "print(f\"{ams.get_input_string()=}\")\n", "# non existing paths will be left alone\n", "ams.EngineRestart = \"bar.rkf\"\n", "print(f\"{ams.get_input_string()=}\")\n", "! rm foo.rkf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Repeated Entry indexing\n", "\n", "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 `[]`. This indexing is not allowed on unique entries." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ams = drivers.AMS()\n", "print(f\"{ams.EngineRestart.unique=}\")\n", "ams.EngineRestart[0] = \"bar.rkf\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hybrid = engines.Hybrid()\n", "hybrid.Engine[0] = engines.ADF()\n", "hybrid.Engine[1] = engines.BAND()\n", "print(hybrid.get_input_string())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hybrid.Engine[3] = engines.DFTB()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setting of block headers\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ams = drivers.AMS()\n", "\n", "print(f\"{ams.System.allow_header=}\")\n", "ams.System.header = \"MyHeader\"\n", "ams.System.Symmetry = \"AUTO\"\n", "print(ams.get_input_string())\n", "\n", "# An exception will be raised when setting the header blocks that don't allow headers\n", "ams.Symmetry.header = \"MyHeader\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Integration with PLAMS\n", "\n", "There are multiple ways to pass your driver object to an instance of `AMSJob`. \n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from scm.plams import AMSJob, Settings, Molecule, Atom\n", "\n", "settings = Settings()\n", "\n", "driver = drivers.AMS()\n", "driver.Task = \"GeometryOptimization\"\n", "driver.Properties.NormalModes = True\n", "driver.Engine = engines.DFTB()\n", "driver.Engine.Model = \"SCC-DFTB\"\n", "\n", "settings.input = driver\n", "\n", "molecule = Molecule()\n", "molecule.add_atom(Atom(symbol=\"O\", coords=(0, 0, 0)))\n", "molecule.add_atom(Atom(symbol=\"H\", coords=(1, 0, 0)))\n", "molecule.add_atom(Atom(symbol=\"H\", coords=(0, 1, 0)))\n", "\n", "\n", "job = AMSJob(molecule=molecule, settings=settings, name=\"water_optimization\")\n", "\n", "print(job.get_input())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "job = AMSJob(molecule=molecule, settings=driver, name=\"water_optimization\")\n", "print(f\"{job.settings.input=}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally you can also create the job object first and start assigning to the `.settings.input` attribute, leading to a slightly more verbose syntax:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "job = AMSJob(molecule=molecule, name=\"water_optimization\")\n", "job.settings.input = drivers.AMS()\n", "job.settings.input.Engine = engines.ADF()\n", "print(job.get_input())\n", "# etc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Conversion to and from PLAMS Settings\n", "\n", "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.\n", "\n", "See the examples below for simple conversions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from scm.input_classes import AMS, DFTB\n", "from scm.plams.interfaces.adfsuite.ams import AMSJob\n", "from scm.plams.core.settings import Settings\n", "\n", "ams = AMS()\n", "ams.Task = \"SinglePoint\"\n", "ams.Engine = DFTB()\n", "\n", "s = Settings()\n", "s.input = ams.to_settings()\n", "job = AMSJob(settings=s)\n", "print(job.get_input())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from scm.input_classes import AMS, DFTB\n", "from scm.plams.interfaces.adfsuite.ams import AMSJob\n", "from scm.plams.core.settings import Settings\n", "\n", "settings = Settings()\n", "settings.input\n", "settings.input.ams.Task = \"GeometryOptimization\"\n", "settings.input.ams.Properties.NormalModes = \"Yes\"\n", "settings.input.DFTB.Model = \"SCC-DFTB\"\n", "\n", "ams = AMS.from_settings(settings)\n", "print(ams.get_input_string())\n", "\n", "# this is supported, but fragile:\n", "dftb = DFTB.from_settings(settings)\n", "print(dftb.get_input_string())\n", "# this is safer:\n", "ams = AMS.from_settings(settings)\n", "dftb = ams.Engine" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Repeated blocks and headers\n", "\n", "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. \n", "\n", "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.\n", "\n", "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.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "driver = drivers.AMS()\n", "\n", "driver.Task = \"Replay\"\n", "driver.Replay.File = \"/foo/bar/ams.rkf\"\n", "\n", "driver.Engine = engines.Hybrid()\n", "driver.Engine.Energy.DynamicFactors = \"UseLowestEnergy\"\n", "# repeated block, start indexing at 0\n", "driver.Engine.Energy.Term[0].Region = \"*\"\n", "driver.Engine.Energy.Term[0].EngineID = \"Singlet\"\n", "driver.Engine.Energy.Term[1].Region = \"*\"\n", "driver.Engine.Energy.Term[1].EngineID = \"Triplet\"\n", "\n", "# create the engine object first, to benefit from type hinting\n", "singlet_engine = engines.ADF()\n", "singlet_engine.header = \"Singlet\"\n", "singlet_engine.Unrestricted = \"No\"\n", "singlet_engine.XC.GGA = \"PBE\"\n", "singlet_engine.Basis.Type = \"DZP\"\n", "singlet_engine.SCF.Iterations = 100\n", "# do not forget to assign it to the repeated engine block\n", "driver.Engine.Engine[0] = singlet_engine\n", "\n", "triplet_engine = engines.ADF()\n", "triplet_engine.header = \"Triplet\"\n", "triplet_engine.Unrestricted = \"Yes\"\n", "triplet_engine.SpinPolarization = 2\n", "triplet_engine.XC.GGA = \"PBE\"\n", "triplet_engine.Basis.Type = \"DZP\"\n", "triplet_engine.SCF.Iterations = 100\n", "driver.Engine.Engine[1] = triplet_engine\n", "\n", "print(driver.get_input_string())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Free blocks\n", "\n", "Free blocks can be assigned with a either a multiline string or a Sequence/Iterable of strings:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "driver = drivers.AMS()\n", "adf = engines.ADF()\n", "\n", "adf.RISM = \"\"\"\\\n", "H 0.0 0.0 0.0\n", "O 1.0 0.0 0.0\n", "O 0.0 1.0 0.0\"\"\"\n", "\n", "driver.Engine = adf\n", "\n", "print(driver.get_input_string())\n", "\n", "adf.RISM = (\n", " \"H 0.0 1.0 0.0\",\n", " \"O 0.0 1.0 0.0\",\n", " \"O 1.0 1.0 1.0\",\n", ")\n", "\n", "print(driver.get_input_string())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Instantiating block instances from existing text input\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "text_input = \"\"\"\\\n", "Task SinglePoint\n", "\n", "Engine BAND\n", " Basis\n", " Type DZP\n", " End\n", " DOS\n", " CalcPDOS True\n", " End\n", " HubbardU\n", " Enabled True\n", " LValue 2 -1\n", " UValue 0.6 0.0\n", " End\n", " KSpace\n", " Quality Basic\n", " End\n", " NumericalQuality Normal\n", " Unrestricted True\n", " XC\n", " gga BP86\n", " End\n", "EndEngine\n", "\"\"\"\n", "\n", "driver = drivers.AMS.from_text(text_input)\n", "\n", "driver.Engine.Basis.Type = \"SZ\"\n", "\n", "print(driver.get_input_string())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.16" } }, "nbformat": 4, "nbformat_minor": 2 }