diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b795ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.tox +.vscode +_build +build diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..5716bfd --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,8 @@ +version: 2 + +conda: + environment: environment.yml + +sphinx: + builder: html + fail_on_warning: false diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/README.md b/README.md index 18a06f9..f2fc11b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ -# aiida-singledocs +# aiida-blog + Single page or other short miscelaneous documentation + + +### To Do: + +* Autoinstall sphinx... diff --git a/docs/archives/visualising_graphs.aiida b/docs/archives/visualising_graphs.aiida new file mode 100644 index 0000000..5616457 Binary files /dev/null and b/docs/archives/visualising_graphs.aiida differ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..302b936 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,116 @@ +# Configuration file for the Sphinx documentation builder. +# + +# -- Project information ----------------------------------------------------- + +project = "aiida-singledocs" +copyright = "2020, Francisco Ramirez" +author = "Francisco Ramirez, Chris Sewell" + + +version = "0.0.1" +release = version + +# -- General configuration --------------------------------------------------- + +needs_sphinx = "2.0" +extensions = ["myst_nb", "sphinx_panels", "ablog", "sphinx.ext.intersphinx"] + +myst_admonition_enable = True +myst_html_img_enable = True +jupyter_execute_notebooks = "auto" + +blog_path = "stories/index" +blog_title = "Stories" +blog_post_pattern = ["stories/*.md", "stories/*.ipynb"] +post_redirect_refresh = 1 +post_auto_excerpt = 2 +post_auto_image = 1 +fontawesome_included = True +html_sidebars = { + "stories/index": ["tagcloud.html", "archives.html", "sbt-sidebar-nav.html"], + "stories/*": [ + "sidebar-search-bs.html", + "postcard.html", + "recentposts.html", + "tagcloud.html", + "categories.html", + "archives.html", + "sbt-sidebar-nav.html", + "sbt-sidebar-footer.html", + ], +} + +intersphinx_mapping = { + "aiida": ("http://aiida-core.readthedocs.io/en/latest/", None), +} + +# The master toctree document. +master_doc = "index" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# -- Options for HTML output ------------------------------------------------- + +html_theme = "sphinx_book_theme" +# html_title = f"version: {version}" +# html_favicon = "_static/quantum-mobile-v4-text-square.png" +# html_logo = "_static/quantum_mobile_text_wide.png" +html_theme_options = { + "home_page_in_toc": True, + "repository_url": "https://github.com/aiidateam/aiida-blog", + "repository_branch": "chris-branch", + "use_repository_button": True, + "use_issues_button": True, + "path_to_docs": "docs", + "use_edit_page_button": True, + "launch_buttons": { + "binderhub_url": "https://https://mybinder.org" + }, +} +panels_add_bootstrap_css = False + + +def start_aiida(*args): + from unittest import mock + import os + import subprocess + + subprocess.check_call(["reentry", "scan"]) + from aiida.manage.external import postgres + from aiida.manage.tests import _GLOBAL_TEST_MANAGER, BACKEND_DJANGO + from aiida.common.utils import Capturing + # this is required on READTHEDOCS, since the docker container only contains C.UTF-8 + patch = mock.patch.object( + postgres, + "_CREATE_DB_COMMAND", + ( + 'CREATE DATABASE "{}" OWNER "{}" ENCODING \'UTF8\' ' + f"LC_COLLATE='{os.environ['LANG']}' LC_CTYPE='{os.environ['LANG']}' " + "TEMPLATE=template0" + ), + ) + + with Capturing(): # capture output of AiiDA DB setup + with patch: + _GLOBAL_TEST_MANAGER.use_temporary_profile(backend=BACKEND_DJANGO, pgtest=None) + from aiida.manage.configuration import settings + + os.environ["AIIDA_PATH"] = settings.AIIDA_CONFIG_FOLDER + + +def end_aiida(*args): + from aiida.manage.tests import _GLOBAL_TEST_MANAGER + + _GLOBAL_TEST_MANAGER.destroy_all() + + +def setup(app): + app.connect("builder-inited", start_aiida) + app.connect("build-finished", end_aiida) diff --git a/docs/images/aiida-crystal17.jpg b/docs/images/aiida-crystal17.jpg new file mode 100644 index 0000000..2f21948 Binary files /dev/null and b/docs/images/aiida-crystal17.jpg differ diff --git a/docs/images/provenance-1.png b/docs/images/provenance-1.png new file mode 100644 index 0000000..74ea721 Binary files /dev/null and b/docs/images/provenance-1.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..af79bc7 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,16 @@ +# Welcome to AiiDA Stories + +This site contains a collection of stories inspired in real life situations and use-cases of AiiDA. +You can use these stories to get a general feeling of what can be done with the code and what is like to work with it, as well as taking the procedures in them as templates for when you need to perform similar tasks. + +Each story will specify at the beginning which version of the [Quantum Mobile](https://www.materialscloud.org/work/quantum-mobile) was used in its design/tests. + +Using the correct virtual machine and creating a new profile that starts from a clean database (see the ``quicksetup`` command in the[`AiiDA documentation](https://aiida.readthedocs.io/projects/aiida-core/en/latest/intro/get_started.html#initialise-data-storage)) ensures the reproducibility of the commands if you want to test them out yourself. + +Any other further set-ups required (such as importing databases) will be described in the introduction of the story. + +```{toctree} +:maxdepth: 1 + +Stories <./stories/index> +``` diff --git a/docs/stories/aiida-crystal17.ipynb b/docs/stories/aiida-crystal17.ipynb new file mode 100644 index 0000000..5f5cbe8 --- /dev/null +++ b/docs/stories/aiida-crystal17.ipynb @@ -0,0 +1,2272 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# aiida-crystal17 calculation\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `crystal17.main` plugin is the core calculation plugin.\n", + "It is designed with a more programmatic\n", + "input interface , to create the input ``.d12`` and ``.gui`` files,\n", + "from a set of AiiDA {py:class}`~aiida.orm.nodes.data.Data` nodes.\n", + "\n", + "![icon](../images/aiida-crystal17.jpg)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{seealso}\n", + "See {ref}`main_calculation_immigrant` for a method\n", + "to immigrate existing output/input files as a\n", + "``crystal17.main`` calculation.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initial Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To run a computation, first ensure AiiDA is running:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m ✓ \u001b[0mprofile: On profile test_profile\u001b[0m\r\n", + "\u001b[32m ✓ \u001b[0mrepository: /var/folders/dm/b2qnkb_n3r72slmpxlfmcjvm00lbnd/T/tmpvsllm_zf/test_repo\u001b[0m\r\n", + "\u001b[32m ✓ \u001b[0mpostgres: Connected as aiida@localhost:61847\u001b[0m\r\n", + "\u001b[32m ✓ \u001b[0mrabbitmq: Connected to amqp://127.0.0.1?heartbeat=600\u001b[0m\r\n", + "\u001b[32m ✓ \u001b[0mdaemon: Daemon is running as PID 38435 since 2019-08-12 11:39:10\u001b[0m\r\n" + ] + } + ], + "source": [ + "!verdi status" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{seealso}\n", + "AiiDA documentation: {ref}`aiida:intro:get_started`\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If `aiida-crystal17` is installed,\n", + "the `crystal17.main` computation should be available:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-\b/\b|\b\\\b-\b/\b\u001b[31m\u001b[1mInputs\u001b[0m\r\n", + "\u001b[1m basissets: required BasisSetData Use a node for the basis set of one of the elements in the structure. You h ...\u001b[0m\r\n", + "\u001b[1m code: required Code The Code to use for this job.\u001b[0m\r\n", + "\u001b[1m parameters: required CryInputParamsData the input parameters to create the .d12 file content.\u001b[0m\r\n", + "\u001b[1m structure: required StructureData structure used to construct the input fort.34 (gui) file\u001b[0m\r\n", + " kinds: optional KindData additional structure kind specific data (e.g. initial spin)\u001b[0m\r\n", + " metadata: optional \u001b[0m\r\n", + " symmetry: optional SymmetryData the symmetry of the structure, used to construct the input .gui file (fort. ...\u001b[0m\r\n", + " wf_folder: optional RemoteData An optional working directory, of a previously completed calculation, conta ...\u001b[0m\r\n", + "\u001b[31m\u001b[1mOutputs\u001b[0m\r\n", + "\u001b[1m remote_folder: required RemoteData Input files necessary to run the process will be stored in this folder node ...\u001b[0m\r\n", + "\u001b[1m results: required Dict the data extracted from the main output file\u001b[0m\r\n", + "\u001b[1m retrieved: required FolderData Files that are retrieved by the daemon will be stored in this node. By defa ...\u001b[0m\r\n", + " optimisation: optional TrajectoryData atomic configurations, for each optimisation step\u001b[0m\r\n", + " structure: optional StructureData the structure output from the calculation\u001b[0m\r\n", + " symmetry: optional SymmetryData the symmetry data from the calculation\u001b[0m\r\n", + "\u001b[31m\u001b[1mExit codes\u001b[0m\r\n", + " 1: The process has failed with an unspecified error.\u001b[0m\r\n", + " 2: The process failed with legacy failure mode.\u001b[0m\r\n", + " 10: The process returned an invalid output.\u001b[0m\r\n", + " 11: The process did not register a required output.\u001b[0m\r\n", + " 200: The retrieved folder data node could not be accessed.\u001b[0m\r\n", + " 210: The main (stdout) output file was not found\u001b[0m\r\n", + " 211: The temporary retrieved folder was not found\u001b[0m\r\n", + " 300: An error was flagged trying to parse the crystal exec stdout file\u001b[0m\r\n", + " 301: An error occurred parsing the 'opta'/'optc' geometry files\u001b[0m\r\n", + " 302: The crystal exec stdout file denoted that the run was a testgeom\u001b[0m\r\n", + " 350: The input file could not be read by crystal\u001b[0m\r\n", + " 351: Crystal could not find the required wavefunction file\u001b[0m\r\n", + " 400: The calculation stopped prematurely because it ran out of walltime.\u001b[0m\r\n", + " 401: The calculation stopped prematurely because it ran out of memory.\u001b[0m\r\n", + " 402: The calculation stopped prematurely because it ran out of virtual memory.\u001b[0m\r\n", + " 411: Scf convergence did not finalise (usually due to reaching step limit)\u001b[0m\r\n", + " 412: Geometry convergence did not finalise (usually due to reaching step limit)\u001b[0m\r\n", + " 413: An error encountered usually during geometry optimisation\u001b[0m\r\n", + " 414: An error was encountered during an scf computation\u001b[0m\r\n", + " 415: An unknown error was encountered, causing the mpi to abort\u001b[0m\r\n", + " 499: The main crystal output file flagged an unhandled error\u001b[0m\r\n", + " 510: Inconsistency in the input and output symmetry\u001b[0m\r\n", + " 520: Primitive symmops were not found in the output file\u001b[0m\r\n" + ] + } + ], + "source": [ + "!verdi plugin list aiida.calculations crystal17.main" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use the python interface,\n", + "first ensure a profile is loaded in the python kernel,\n", + "and import the required modules:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "init_cell": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'test_crystal17'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from aiida import load_profile\n", + "profile = load_profile()\n", + "profile.name" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "init_cell": true + }, + "outputs": [], + "source": [ + "import os\n", + "from six import StringIO\n", + "from aiida.orm import Code\n", + "from aiida.plugins import (\n", + " DataFactory, WorkflowFactory, CalculationFactory)\n", + "from aiida.engine import run_get_node\n", + "from aiida_crystal17.common import display_json\n", + "from aiida_crystal17.tests import read_resource_text, resource_context\n", + "from aiida.tools.visualization import Graph\n", + "from jsonextended import edict" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Input Node Creation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{seealso}\n", + "[CRYSTAL17 Manual](http://www.crystal.unito.it/Manuals/crystal17.pdf)\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{seealso}\n", + "AiiDA documentation: {ref}`aiida:how-to:run-codes`\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An {py:class}`~aiida.orm.nodes.data.code.Code` node should be set up in advance,\n", + "to use the `crystal17.basic` calculation plugin,\n", + "and call the ``runcry17`` executable\n", + "(or ``mock_runcry17`` used here for test purposes)." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[['PK', 1],\n", + " ['UUID', 'a2241a9c-deac-4960-9812-088666800dee'],\n", + " ['Label', 'crystal17.main-mock_crystal17@localhost'],\n", + " ['Description', ''],\n", + " ['Default plugin', 'crystal17.main'],\n", + " ['Type', 'remote'],\n", + " ['Remote machine', 'localhost'],\n", + " ['Remote absolute path',\n", + " '//anaconda/envs/aiida_crystal17/bin/mock_crystal17'],\n", + " ['Prepend text', 'No prepend text'],\n", + " ['Append text', 'No append text']]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from aiida_crystal17.tests.utils import get_or_create_local_computer, get_or_create_code\n", + "computer = get_or_create_local_computer('work_directory', 'localhost')\n", + "code = get_or_create_code('crystal17.main', computer, 'mock_crystal17')\n", + "code.get_full_text_info()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Input Parameters (Geometry Independent)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The {py:class}`~aiida_crystal17.data.input_params.CryInputParamsData`\n", + "supplies (geometry independent) data required to create the `input.d12` file." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "param_dict = {\"scf\":{\"k_points\": (8, 8)}}\n", + "params = DataFactory('crystal17.parameters')(data=param_dict)\n", + "params" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The input data is validated against the {ref}`cry_main_input_schema`,\n", + "which can also be obtained from the `data_schema` attribute.\n", + "\n", + ":::{note}\n", + "The only mandated key is ``scf.k_points`` (known as ``SHRINK`` in CRYSTAL17)\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m$schema\u001b[0m: http://json-schema.org/draft-04/schema#\n", + "\u001b[34madditionalProperties\u001b[0m: False\n", + "\u001b[34mdescription\u001b[0m: Allowed Inputs For CRYSTAL17 .d12 file\n", + "\u001b[34mproperties\u001b[0m:\n", + " \u001b[34mbasis_set\u001b[0m:\n", + " \u001b[34madditionalProperties\u001b[0m: False\n", + " \u001b[34mdescription\u001b[0m: Basis sets input and control\n", + " \u001b[34mproperties\u001b[0m: {...}\n", + " \u001b[34mtitle\u001b[0m: Block 2\n", + " \u001b[34mtype\u001b[0m: object\n", + " \u001b[34mgeometry\u001b[0m:\n", + " \u001b[34madditionalProperties\u001b[0m: False\n", + " \u001b[34mdescription\u001b[0m: Geometry input, manipulation and optimisation control\n", + " \u001b[34mproperties\u001b[0m: {...}\n", + " \u001b[34mtitle\u001b[0m: Block 1\n", + " \u001b[34mtype\u001b[0m: object\n", + " \u001b[34mscf\u001b[0m:\n", + " \u001b[34madditionalProperties\u001b[0m: False\n", + " \u001b[34mdependencies\u001b[0m: {...}\n", + " \u001b[34mdescription\u001b[0m: Single particle Hamiltonian and SCF control\n", + " \u001b[34mproperties\u001b[0m: {...}\n", + " \u001b[34mrequired\u001b[0m: [k_points]\n", + " \u001b[34mtitle\u001b[0m: Block 3\n", + " \u001b[34mtype\u001b[0m: object\n", + " \u001b[34mtitle\u001b[0m:\n", + " \u001b[34mdescription\u001b[0m: the title of the run\n", + " \u001b[34mtype\u001b[0m: string\n", + "\u001b[34mrequired\u001b[0m: [scf]\n", + "\u001b[34mtitle\u001b[0m: CRYSTAL17 Input\n", + "\u001b[34mtype\u001b[0m: object\n" + ] + } + ], + "source": [ + "param_cls = DataFactory('crystal17.parameters')\n", + "edict.pprint(params.data_schema, keycolor=\"blue\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "tags": [ + "raises-exception" + ] + }, + "outputs": [ + { + "ename": "ValidationError", + "evalue": "- 'k_points' is a required property [key path: 'scf']", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValidationError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mparams\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mDataFactory\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'crystal17.parameters'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0;34m\"scf\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/Users/cjs14/GitHub/aiida-crystal17/aiida_crystal17/data/input_params.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, data, unflatten, **kwargs)\u001b[0m\n\u001b[1;32m 61\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0munflatten\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 62\u001b[0m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0munflatten_dict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 63\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_data\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 64\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 65\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_validate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/Users/cjs14/GitHub/aiida-crystal17/aiida_crystal17/data/input_params.py\u001b[0m in \u001b[0;36mset_data\u001b[0;34m(self, data)\u001b[0m\n\u001b[1;32m 79\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 80\u001b[0m \u001b[0;31m# first validate the inputs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 81\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalidate_parameters\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 82\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 83\u001b[0m \u001b[0;31m# store all but the symmetry operations as attributes\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/Users/cjs14/GitHub/aiida-crystal17/aiida_crystal17/data/input_params.py\u001b[0m in \u001b[0;36mvalidate_parameters\u001b[0;34m(cls, dct)\u001b[0m\n\u001b[1;32m 44\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 45\u001b[0m \"\"\"\n\u001b[0;32m---> 46\u001b[0;31m \u001b[0mvalidate_against_schema\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdct\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcls\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata_schema\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 47\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 48\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0munflatten\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/Users/cjs14/GitHub/aiida-crystal17/aiida_crystal17/validation/utils.py\u001b[0m in \u001b[0;36mvalidate_against_schema\u001b[0;34m(data, schema)\u001b[0m\n\u001b[1;32m 109\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merrors\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 110\u001b[0m raise jsonschema.ValidationError('\\n'.join([\n\u001b[0;32m--> 111\u001b[0;31m \u001b[0;34m\"- {} [key path: '{}']\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merror\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmessage\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'/'\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mp\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mp\u001b[0m \u001b[0;32min\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0merror\u001b[0m \u001b[0;32min\u001b[0m \u001b[0merrors\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 112\u001b[0m ]))\n\u001b[1;32m 113\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mValidationError\u001b[0m: - 'k_points' is a required property [key path: 'scf']" + ] + } + ], + "source": [ + "params = DataFactory('crystal17.parameters')(data={\"scf\": {}})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is also possible to reverse engineer the input data,\n", + "from an existing input file, using\n", + "{py:class}`~aiida_crystal17.parsers.raw.inputd12_read.extract_data`,\n", + "which is also exposed on the command line as `verdi data crystal17.parse stdin`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'title': 'MgO bulk',\n", + " 'geometry': {'optimise': {'type': 'FULLOPTG'}},\n", + " 'scf': {'dft': {'xc': 'B3LYP', 'SPIN': True},\n", + " 'k_points': (8, 8),\n", + " 'fock_mixing': 'ANDERSON',\n", + " 'numerical': {'SMEAR': 0.1},\n", + " 'post_scf': ['PPAN']}}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from aiida_crystal17.parsers.raw.inputd12_read import extract_data\n", + "param_dict, basis_sets, atom_props = extract_data(\"\"\"\\\n", + "MgO bulk\n", + "EXTERNAL\n", + "OPTGEOM\n", + "FULLOPTG\n", + "END\n", + "END\n", + "12 3\n", + "1 0 3 2. 0.\n", + "1 1 3 8. 0.\n", + "1 1 3 2. 0.\n", + "8 2\n", + "1 0 3 2. 0.\n", + "1 1 3 6. 0.\n", + "99 0\n", + "END\n", + "DFT\n", + "B3LYP\n", + "SPIN\n", + "END\n", + "SHRINK\n", + "8 8\n", + "ANDERSON\n", + "SMEAR\n", + "0.1\n", + "ATOMSPIN\n", + "2\n", + "1 1 2 -1\n", + "PPAN\n", + "END\n", + "\"\"\")\n", + "param_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MgO bulk\n", + "EXTERNAL\n", + "OPTGEOM\n", + "FULLOPTG\n", + "ENDOPT\n", + "END\n", + "12 3\n", + "1 0 3 2. 0.\n", + "1 1 3 8. 0.\n", + "1 1 3 2. 0.\n", + "8 2\n", + "1 0 3 2. 0.\n", + "1 1 3 6. 0.\n", + "99 0\n", + "END\n", + "DFT\n", + "B3LYP\n", + "SPIN\n", + "END\n", + "SHRINK\n", + "8 8\n", + "ATOMSPIN\n", + "2\n", + "1 1\n", + "2 -1\n", + "SMEAR\n", + "0.1\n", + "ANDERSON\n", + "PPAN\n", + "END\n", + "\n" + ] + } + ], + "source": [ + "from aiida_crystal17.parsers.raw.inputd12_write import write_input\n", + "print(write_input(param_dict, basis_sets, atom_props))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Atomic Structure" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ``structure`` refers to a standard\n", + "{py:class}`~aiida.StructureData` node, and is used to create the `main.gui`.\n", + "\n", + "Structures consist of:\n", + "\n", + "- A cell with a basis vectors and whether it is periodic, for each dimension\n", + "- ``Site`` with a cartesian coordinate and reference to a kind\n", + "- ``Kind`` which details the species and composition at one or more sites\n", + "\n", + "The simplest way to create a structure is *via* {py:mod}`ase`:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from ase.spacegroup import crystal\n", + "atoms = crystal(\n", + " symbols=[12, 8],\n", + " basis=[[0, 0, 0], [0.5, 0.5, 0.5]],\n", + " spacegroup=225,\n", + " cellpar=[4.21, 4.21, 4.21, 90, 90, 90])\n", + "struct_cls = DataFactory('structure')\n", + "structure = struct_cls(ase=atoms)\n", + "structure" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These structures can be visualised using standard ASE methods." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "ipub": { + "figure": { + "caption": "Structure visualisation, using ASE and Matplotlib." + } + } + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "from ase.visualize.plot import plot_atoms\n", + "atoms = structure.get_ase()\n", + "fig, ax = plt.subplots()\n", + "plot_atoms(atoms.repeat((2,2,2)),\n", + " ax, radii=0.8, show_unit_cell=True,\n", + " rotation=('45x,0y,0z'));" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As default, one kind is created per atomic species\n", + "(named as the atomic symbol):" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Mg', 'Mg', 'Mg', 'Mg', 'O1', 'O1', 'O1', 'O1']" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "structure.get_site_kindnames()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, we may want to specify more than one kind per species\n", + "(for example to setup anti-ferromagnetic spin).\n", + "We can achieve this by tagging the atoms:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Mg1', 'Mg1', 'Mg2', 'Mg2', 'O', 'O', 'O', 'O']" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "atoms_afm = atoms.copy()\n", + "atoms_afm.set_tags([1, 1, 2, 2, 0, 0, 0, 0])\n", + "structure_afm = struct_cls(ase=atoms_afm)\n", + "structure_afm.get_site_kindnames()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{note}\n", + "Since we **always** use the ``EXTERNAL`` keyword for geometry,\n", + "any manipulation to the geometry is undertaken before calling CRYSTAL\n", + "(i.e. we delegate the responsibility for geometry away from CRYSTAL).\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Kind Specific Parameters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Also, we may want to add atom specific inputs to the ``.d12``,\n", + "such as initial spin and frozen atoms (for optimisation)." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```json\n", + "{\n", + " \"Mg1\": {\n", + " \"spin_alpha\": true,\n", + " \"spin_beta\": false,\n", + " \"fixed\": false,\n", + " \"ghosts\": false\n", + " },\n", + " \"Mg2\": {\n", + " \"spin_alpha\": false,\n", + " \"spin_beta\": true,\n", + " \"fixed\": false,\n", + " \"ghosts\": false\n", + " },\n", + " \"O\": {\n", + " \"spin_alpha\": false,\n", + " \"spin_beta\": false,\n", + " \"fixed\": true,\n", + " \"ghosts\": false\n", + " }\n", + "}\n", + "```" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "kind_cls = DataFactory(\"crystal17.kinds\")\n", + "kind_data = kind_cls(data={\n", + " \"kind_names\": [\"Mg1\", \"Mg2\", \"O\"],\n", + " \"spin_alpha\": [True, False, False],\n", + " \"spin_beta\": [False, True, False],\n", + " \"fixed\": [False, False, True],\n", + " \"ghosts\": [False, False, False]\n", + "})\n", + "display_json(kind_data.kind_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'spin_alpha': [1, 2],\n", + " 'spin_beta': [3, 4],\n", + " 'unfixed': [1, 2, 3, 4],\n", + " 'ghosts': []}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from aiida_crystal17.parsers.raw.inputd12_write import create_atom_properties\n", + "atom_props2 = create_atom_properties(structure_afm, kind_data)\n", + "atom_props2" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MgO bulk\n", + "EXTERNAL\n", + "OPTGEOM\n", + "FULLOPTG\n", + "FRAGMENT\n", + "4\n", + "1 2 3 4\n", + "ENDOPT\n", + "END\n", + "12 3\n", + "1 0 3 2. 0.\n", + "1 1 3 8. 0.\n", + "1 1 3 2. 0.\n", + "8 2\n", + "1 0 3 2. 0.\n", + "1 1 3 6. 0.\n", + "99 0\n", + "END\n", + "DFT\n", + "B3LYP\n", + "SPIN\n", + "END\n", + "SHRINK\n", + "8 8\n", + "ATOMSPIN\n", + "4\n", + "1 1\n", + "2 1\n", + "3 -1\n", + "4 -1\n", + "SMEAR\n", + "0.1\n", + "ANDERSON\n", + "PPAN\n", + "END\n", + "\n" + ] + } + ], + "source": [ + "print(write_input(param_dict, basis_sets, atom_props2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Basis Sets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Basis sets are stored as separate\n", + "{py:class}`~aiida_crystal17.data.basis_set.BasisSetData` nodes,\n", + "in a similar fashion to {py:class}`~aiida.orm.nodes.data.upf.UpfData`.\n", + "They are created individually from a text file,\n", + "which contains the content of the basis set\n", + "and (optionally) a YAML style header section, fenced by ``---``:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---\n", + "author: John Smith\n", + "year: 1999\n", + "class: sto3g\n", + "---\n", + "12 3\n", + "1 0 3 2. 0.\n", + "1 1 3 8. 0.\n", + "1 1 3 2. 0.\n" + ] + } + ], + "source": [ + "mg_basis_content = read_resource_text('basis_sets', 'sto3g', 'sto3g_Mg.basis')\n", + "print(mg_basis_content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The attributes of the basis set are stored in the database,\n", + "and the md5 hash-sum is used to test equivalence of two basis sets." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```json\n", + "{\n", + " \"md5\": \"0731ecc3339d2b8736e61add113d0c6f\",\n", + " \"year\": 1999,\n", + " \"class\": \"sto3g\",\n", + " \"author\": \"John Smith\",\n", + " \"element\": \"Mg\",\n", + " \"filename\": \"stringio.txt\",\n", + " \"basis_type\": \"all-electron\",\n", + " \"num_shells\": 3,\n", + " \"atomic_number\": 12,\n", + " \"orbital_types\": [\n", + " \"S\",\n", + " \"SP\",\n", + " \"SP\"\n", + " ]\n", + "}\n", + "```" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'12 3\\n1 0 3 2. 0.\\n1 1 3 8. 0.\\n1 1 3 2. 0.'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "basis_cls = DataFactory('crystal17.basisset')\n", + "mg_basis, created = basis_cls.get_or_create(StringIO(mg_basis_content))\n", + "display_json(mg_basis.attributes)\n", + "mg_basis.content" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A simpler way to create and refer to basis sets, is *via* a **family group**.\n", + "All basis sets in a folder can be read and saved to a named family by:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Mg': ,\n", + " 'Ni': ,\n", + " 'O': }" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with resource_context('basis_sets', 'sto3g') as path:\n", + " nfiles, nuploaded = basis_cls.upload_basisset_family(\n", + " path,\n", + " \"sto3g\", \"group of sto3g basis sets\",\n", + " extension=\".basis\", stop_if_existing=False)\n", + "basis_cls.get_basis_group_map(\"sto3g\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or at the command line:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Usage: verdi data crystal17.basis uploadfamily [OPTIONS]\r\n", + "\r\n", + " Upload a family of CRYSTAL Basis Set files.\r\n", + "\r\n", + "Options:\r\n", + " --path PATH Path to a folder containing the Basis Set\r\n", + " files\r\n", + " --ext TEXT the file extension to filter by\r\n", + " --name TEXT Name of the BasisSet family [required]\r\n", + " -D, --description DESCRIPTION A description for the family\r\n", + " --stop-if-existing Abort when encountering a previously uploaded\r\n", + " Basis Set file\r\n", + " --dry-run do not commit to database or modify\r\n", + " configuration files\r\n", + " -h, --help Show this message and exit.\r\n" + ] + } + ], + "source": [ + "!verdi data crystal17.basis uploadfamily --help" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-\b/\b|\b\\\b-\b/\bFamily Num Basis Sets\r\n", + "-------- ----------------\r\n", + "sto3g 3\r\n", + "\r\n" + ] + } + ], + "source": [ + "!verdi data crystal17.basis listfamilies" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Basis families can be searched by the elements they contain:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "basis_cls.get_basis_groups([\"Ni\", \"O\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Basis sets can also be extracted for a particular structure." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Mg': ,\n", + " 'O': }" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "basis_cls.get_basissets_from_structure(structure, \"sto3g\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{important}\n", + "Unlike `aiida-quantumespresso.pw`,\n", + "``crystal17.main`` uses one basis sets per atomic number only **NOT** per kind.\n", + "This is because, using multiple basis sets per atomic number is rarely used in CRYSTAL17,\n", + "and is limited anyway to only two types per atomic number.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Symmetry" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the ``main.gui`` file,\n", + "as well as using the dimensionality (i.e. periodic boundary conditions),\n", + "basis vectors and atomic positions, provided by the ``structure``,\n", + "we also need to specify the atomic symmetry of the structure.\n", + "\n", + "{py:class}`~aiida_crystal17.data.symmetry.SymmetryData` is used to store this data, as a validated dictionary.\n", + "\n", + ":::{note}\n", + "The ``operations`` are given as a flattened version of the rotation matrix,\n", + "followed by the translation vector.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m$schema\u001b[0m: http://json-schema.org/draft-07/schema\n", + "\u001b[34madditionalProperties\u001b[0m: True\n", + "\u001b[34mproperties\u001b[0m:\n", + " \u001b[34mbasis\u001b[0m:\n", + " \u001b[34mdescription\u001b[0m: whether the symmetry operations are fractional or cartesian\n", + " \u001b[34menum\u001b[0m: [fractional, cartesian]\n", + " \u001b[34mtype\u001b[0m: string\n", + " \u001b[34mcomputation\u001b[0m:\n", + " \u001b[34mdescription\u001b[0m: details of the computation\n", + " \u001b[34mtype\u001b[0m: object\n", + " \u001b[34mequivalent_sites\u001b[0m:\n", + " \u001b[34mdescription\u001b[0m: mapping table to equivalent atomic sites\n", + " \u001b[34mitems\u001b[0m: {...}\n", + " \u001b[34mtype\u001b[0m: array\n", + " \u001b[34mhall_number\u001b[0m:\n", + " \u001b[34mdescription\u001b[0m: Hall number defining the symmetry group\n", + " \u001b[34mmaximum\u001b[0m: 530\n", + " \u001b[34mminimum\u001b[0m: 1\n", + " \u001b[34mtype\u001b[0m: [null, integer]\n", + " \u001b[34moperations\u001b[0m:\n", + " \u001b[34mdescription\u001b[0m: symmetry operations, should at least include the unity\n", + " operation\n", + " \u001b[34mitems\u001b[0m: {...}\n", + " \u001b[34mminItems\u001b[0m: 1\n", + " \u001b[34mtype\u001b[0m: array\n", + " \u001b[34muniqueItems\u001b[0m: True\n", + "\u001b[34mrequired\u001b[0m: [hall_number, operations, basis]\n", + "\u001b[34mtitle\u001b[0m: structure symmetry settings\n", + "\u001b[34mtype\u001b[0m: object\n" + ] + } + ], + "source": [ + "symmetry_cls = DataFactory(\"crystal17.symmetry\")\n", + "edict.pprint(symmetry_cls.data_schema, keycolor=\"blue\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The simplest symmetry would be the unitary operator." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'hall_number': 1, 'basis': 'fractional', 'num_symops': 1}" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "symmetry = symmetry_cls(data={\n", + " \"hall_number\": 1,\n", + " \"basis\": \"fractional\",\n", + " \"operations\": [\n", + " [1,0,0,0,1,0,0,0,1,0,0,0]\n", + " ]\n", + "})\n", + "symmetry.attributes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The full symmetry operations of a periodic structure,\n", + "can be computed using the `crystal17.sym3d` workflow.\n", + "This uses the `spglib `_ library\n", + "to compute symmetries, but with the added constraint that sites\n", + "with the same ``Kind`` must be symmetrically equivalent." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-\b/\b|\b\\\b-\b/\b\u001b[31m\u001b[1mInputs\u001b[0m\r\n", + "\u001b[1m settings: required Dict \u001b[0m\r\n", + " cif: optional CifData \u001b[0m\r\n", + " metadata: optional \u001b[0m\r\n", + " structure: optional StructureData \u001b[0m\r\n", + "\u001b[31m\u001b[1mOutputs\u001b[0m\r\n", + "\u001b[1m symmetry: required SymmetryData \u001b[0m\r\n", + " structure: optional StructureData \u001b[0m\r\n", + "\u001b[31m\u001b[1mExit codes\u001b[0m\r\n", + " 1: The process has failed with an unspecified error.\u001b[0m\r\n", + " 2: The process failed with legacy failure mode.\u001b[0m\r\n", + " 10: The process returned an invalid output.\u001b[0m\r\n", + " 11: The process did not register a required output.\u001b[0m\r\n", + " 300: One of either a structure or cif input must be supplied\u001b[0m\r\n", + " 301: The supplied structure must be 3d (i.e. have all dimensions pbc=true)\"\u001b[0m\r\n", + " 302: Idealize can only be used when standardize=true\u001b[0m\r\n", + " 303: The kind names supplied are not compatible with the structure\u001b[0m\r\n", + " 304: Error creating new structure\u001b[0m\r\n", + " 305: Error computing symmetry operations\u001b[0m\r\n" + ] + } + ], + "source": [ + "!verdi plugin list aiida.workflows crystal17.sym3d" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "sym3d_cls = WorkflowFactory(\"crystal17.sym3d\")\n", + "builder = sym3d_cls.get_builder()\n", + "builder.settings = {\"symprec\": 0.01}\n", + "builder.structure = structure\n", + "sym_result = run_get_node(builder)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "ipub": { + "figure": { + "caption": "`crystal17.sym3d`workflow provenance graph." + } + } + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "N9\n", + "\n", + "SymmetryData (9)\n", + "hall_number: 523\n", + "symmops: 192\n", + "\n", + "\n", + "\n", + "N7\n", + "\n", + "Symmetrise3DStructure (7)\n", + "State: finished\n", + "Exit Code: 0\n", + "\n", + "\n", + "\n", + "N7->N9\n", + "\n", + "\n", + "RETURN\n", + "symmetry\n", + "\n", + "\n", + "\n", + "N8\n", + "\n", + "compute_symmetry (8)\n", + "State: finished\n", + "Exit Code: 0\n", + "\n", + "\n", + "\n", + "N7->N8\n", + "\n", + "\n", + "CALL_CALC\n", + "CALL\n", + "\n", + "\n", + "\n", + "N8->N9\n", + "\n", + "\n", + "CREATE\n", + "result\n", + "\n", + "\n", + "\n", + "N6\n", + "\n", + "StructureData (6)\n", + "Mg4O4\n", + "\n", + "\n", + "\n", + "N6->N7\n", + "\n", + "\n", + "INPUT_WORK\n", + "structure\n", + "\n", + "\n", + "\n", + "N6->N8\n", + "\n", + "\n", + "INPUT_CALC\n", + "structure\n", + "\n", + "\n", + "\n", + "N5\n", + "\n", + "Dict (5)\n", + "\n", + "\n", + "\n", + "N5->N7\n", + "\n", + "\n", + "INPUT_WORK\n", + "settings\n", + "\n", + "\n", + "\n", + "N5->N8\n", + "\n", + "\n", + "INPUT_CALC\n", + "settings\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph = Graph(graph_attr={'size': \"8,8!\", \"rankdir\": \"LR\"})\n", + "graph.recurse_ancestors(sym_result.result[\"symmetry\"],\n", + " annotate_links=\"both\")\n", + "graph.graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This workflow can also optionally compute the primitive\n", + "and/or standardised form of the structure,\n", + "before computing the symmetry of the new structure." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "ipub": { + "figure": { + "caption": "`crystal17.sym3d`workflow provenance graph, including primitive cell calculation." + } + } + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "N13\n", + "\n", + "StructureData (13)\n", + "MgO\n", + "\n", + "\n", + "\n", + "N14\n", + "\n", + "compute_symmetry (14)\n", + "State: finished\n", + "Exit Code: 0\n", + "\n", + "\n", + "\n", + "N13->N14\n", + "\n", + "\n", + "INPUT_CALC\n", + "structure\n", + "\n", + "\n", + "\n", + "N11\n", + "\n", + "Symmetrise3DStructure (11)\n", + "State: finished\n", + "Exit Code: 0\n", + "\n", + "\n", + "\n", + "N11->N13\n", + "\n", + "\n", + "RETURN\n", + "structure\n", + "\n", + "\n", + "\n", + "N12\n", + "\n", + "standard_primitive_structure (12)\n", + "State: finished\n", + "Exit Code: 0\n", + "\n", + "\n", + "\n", + "N11->N12\n", + "\n", + "\n", + "CALL_CALC\n", + "CALL\n", + "\n", + "\n", + "\n", + "N15\n", + "\n", + "SymmetryData (15)\n", + "hall_number: 523\n", + "symmops: 48\n", + "\n", + "\n", + "\n", + "N11->N15\n", + "\n", + "\n", + "RETURN\n", + "symmetry\n", + "\n", + "\n", + "\n", + "N11->N14\n", + "\n", + "\n", + "CALL_CALC\n", + "CALL\n", + "\n", + "\n", + "\n", + "N12->N13\n", + "\n", + "\n", + "CREATE\n", + "result\n", + "\n", + "\n", + "\n", + "N6\n", + "\n", + "StructureData (6)\n", + "Mg4O4\n", + "\n", + "\n", + "\n", + "N6->N11\n", + "\n", + "\n", + "INPUT_WORK\n", + "structure\n", + "\n", + "\n", + "\n", + "N6->N12\n", + "\n", + "\n", + "INPUT_CALC\n", + "structure\n", + "\n", + "\n", + "\n", + "N10\n", + "\n", + "Dict (10)\n", + "\n", + "\n", + "\n", + "N10->N11\n", + "\n", + "\n", + "INPUT_WORK\n", + "settings\n", + "\n", + "\n", + "\n", + "N10->N12\n", + "\n", + "\n", + "INPUT_CALC\n", + "settings\n", + "\n", + "\n", + "\n", + "N10->N14\n", + "\n", + "\n", + "INPUT_CALC\n", + "settings\n", + "\n", + "\n", + "\n", + "N14->N15\n", + "\n", + "\n", + "CREATE\n", + "result\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "builder.settings = {\n", + " \"symprec\": 0.01,\n", + " \"compute_primitive\": True,\n", + " \"standardize_cell\": True}\n", + "sym_result2 = run_get_node(builder)\n", + "graph = Graph(graph_attr={'size': \"9,9!\", \"rankdir\": \"LR\"})\n", + "graph.recurse_ancestors(sym_result2.result[\"structure\"],\n", + " annotate_links=\"both\")\n", + "graph.recurse_ancestors(sym_result2.result[\"symmetry\"],\n", + " annotate_links=\"both\")\n", + "graph.graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The other option is to ``idealize`` the structure, which\n", + "removes distortions of the unit cell's atomic positions,\n", + "compared to the ideal symmetry." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting Up and Running the Calculation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{seealso}\n", + "AiiDA documentation: {ref}`aiida:topics:processes`\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "{py:class}`~aiida_crystal17.calculations.cry_main.CryMainCalculation`\n", + "provides a helper function to create, populate and validate the input builder." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```json\n", + "{\n", + " \"metadata\": {\n", + " \"options\": {\n", + " \"resources\": {\n", + " \"num_machines\": 1,\n", + " \"num_mpiprocs_per_machine\": 1\n", + " }\n", + " }\n", + " },\n", + " \"basissets\": {\n", + " \"Ni\": [\n", + " \"uuid: dbf86b0d-0f68-4733-930f-ea48d8f2946d (pk: 3)\"\n", + " ],\n", + " \"O\": [\n", + " \"uuid: 93a07f42-b17c-42ab-a7b8-620b6ccd9a39 (pk: 4)\"\n", + " ]\n", + " },\n", + " \"parameters\": [\n", + " \"uuid: c594fdb9-9fc2-4e06-a52d-7b187b1a4cfa (unstored)\"\n", + " ],\n", + " \"structure\": [\n", + " \"uuid: 000fd6e6-4253-44aa-baae-6b45bb7d5fc7 (pk: 20)\"\n", + " ],\n", + " \"symmetry\": [\n", + " \"uuid: 93db2f1e-0949-4e62-a14b-0d68e2d114ad (pk: 22)\"\n", + " ],\n", + " \"kinds\": [\n", + " \"uuid: 7481e639-81c2-4227-b35b-1849d8c2748f (unstored)\"\n", + " ],\n", + " \"code\": [\n", + " \"Remote code 'crystal17.main-mock_crystal17@localhost' on localhost,\",\n", + " \"pk: 1, uuid: a2241a9c-deac-4960-9812-088666800dee\"\n", + " ]\n", + "}\n", + "```" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from aiida_crystal17.tests import get_test_structure_and_symm\n", + "from aiida_crystal17.data.kinds import KindData\n", + "\n", + "calc_cls = CalculationFactory('crystal17.main')\n", + "structure, symmetry = get_test_structure_and_symm(\"NiO_afm\")\n", + "kind_data = KindData(data={\n", + " \"kind_names\": [\"Ni1\", \"Ni2\", \"O\"],\n", + " \"spin_alpha\": [True, False, False],\n", + " \"spin_beta\": [False, True, False]})\n", + "\n", + "calc_builder = calc_cls.create_builder(\n", + " parameters={\n", + " \"title\": \"NiO Bulk with AFM spin\",\n", + " \"scf.single\": \"UHF\",\n", + " \"scf.k_points\": (8, 8),\n", + " \"scf.spinlock.SPINLOCK\": (0, 15),\n", + " \"scf.numerical.FMIXING\": 30,\n", + " \"scf.post_scf\": [\"PPAN\"]\n", + " },\n", + " unflatten=True,\n", + " structure=structure,\n", + " symmetry=symmetry,\n", + " kinds=kind_data,\n", + " bases=\"sto3g\",\n", + " code=code,\n", + " metadata={\"options\": {\"resources\": {\n", + " \"num_machines\": 1, \"num_mpiprocs_per_machine\": 1}}}\n", + ")\n", + "display_json(calc_builder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to run the computation,\n", + "the builder can be parsed to one of the AiiDA ``run`` (blocking execution) or ``submit`` (non-blocking execution) functions:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "tags": [ + "nbreg_compare_output" + ] + }, + "outputs": [], + "source": [ + "result, calcnode = run_get_node(calc_builder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The process can be monitored on the command line:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-\b/\b|\b\\\b-\b/\b|\b\u001b[22m PK Created Process label Process State Process status\r\n", + "---- --------- --------------------- --------------- ----------------\r\n", + " 25 15s ago CryMainCalculation ⏹ Finished [0]\r\n", + " 21 15s ago compute_symmetry ⏹ Finished [0]\r\n", + " 19 16s ago primitive_structure ⏹ Finished [0]\r\n", + " 18 16s ago Symmetrise3DStructure ⏹ Finished [0]\u001b[0m\r\n", + "\u001b[22m\r\n", + "Total results: 4\r\n", + "\u001b[0m\r\n", + "\u001b[34m\u001b[1mInfo: \u001b[0m\u001b[22mlast time an entry changed state: 2s ago (at 10:39:42 on 2019-08-12)\u001b[0m\r\n" + ] + } + ], + "source": [ + "!verdi process list -a -D desc -l 4" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the calculation is complete, a ``CalcJobNode`` will be created,\n", + "to store the settings and outcome of the computation.\n", + "Crucially, if the computation has completed successfully,\n", + "the `exit_status` will be **0**.\n", + "\n", + "This can be assessed on the command line or with the python API." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-\b/\b|\b\\\b-\b/\b|\b\u001b[22mProperty Value\r\n", + "------------- ------------------------------------\r\n", + "type CalcJobNode\r\n", + "pk 25\r\n", + "uuid 42166254-7530-4e9e-a7f8-bef09e6a6e6f\r\n", + "label\r\n", + "description\r\n", + "ctime 2019-08-12 10:39:28.969540+00:00\r\n", + "mtime 2019-08-12 10:39:42.274641+00:00\r\n", + "process state Finished\r\n", + "exit status 0\r\n", + "computer [1] localhost\r\n", + "\r\n", + "Inputs PK Type\r\n", + "---------- ---- ------------------\r\n", + "basissets\r\n", + " O 4 BasisSetData\r\n", + " Ni 3 BasisSetData\r\n", + "code 1 Code\r\n", + "kinds 24 KindData\r\n", + "parameters 23 CryInputParamsData\r\n", + "structure 20 StructureData\r\n", + "symmetry 22 SymmetryData\r\n", + "\r\n", + "Outputs PK Type\r\n", + "------------- ---- ----------\r\n", + "remote_folder 26 RemoteData\r\n", + "results 28 Dict\r\n", + "retrieved 27 FolderData\u001b[0m\r\n" + ] + } + ], + "source": [ + "!verdi process show {calcnode.pk}" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "tags": [ + "nbreg_compare_output" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n", + "ProcessState.FINISHED\n", + "0\n" + ] + } + ], + "source": [ + "print(calcnode.is_finished_ok)\n", + "print(calcnode.process_state)\n", + "print(calcnode.exit_status)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the calculation fails, there are three things that should be checked:\n", + "\n", + "1. The calculation's exit_message\n", + "2. The calculation's log messages and scheduler output\n", + "3. The `results` output node (if available)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exit Message: None\n", + "*** 25: None\n", + "*** (empty scheduler output file)\n", + "*** (empty scheduler errors file)\n", + "*** 0 LOG MESSAGES\n" + ] + } + ], + "source": [ + "print(\"Exit Message:\", calcnode.exit_message)\n", + "from aiida.cmdline.utils.common import get_calcjob_report\n", + "print(get_calcjob_report(calcnode))" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-\b/\b|\b\\\b-\b/\b|\b\u001b[22m*** 25: None\r\n", + "*** (empty scheduler output file)\r\n", + "*** (empty scheduler errors file)\r\n", + "*** 0 LOG MESSAGES\u001b[0m\r\n" + ] + } + ], + "source": [ + "!verdi process report {calcnode.pk}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analysis of Outputs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The {py:class}`~aiida.tools.visualization.graph.Graph` can be used to visualise the calculations provenance graph:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "ipub": { + "figure": { + "caption": "`crystal17.main` calculation provenance graph (SCF only)." + } + } + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "N25\n", + "\n", + "CryMainCalculation (25)\n", + "State: finished\n", + "Exit Code: 0\n", + "\n", + "\n", + "\n", + "N28\n", + "\n", + "Dict (28)\n", + "\n", + "\n", + "\n", + "N25->N28\n", + "\n", + "\n", + "CREATE\n", + "results\n", + "\n", + "\n", + "\n", + "N27\n", + "\n", + "FolderData (27)\n", + "\n", + "\n", + "\n", + "N25->N27\n", + "\n", + "\n", + "CREATE\n", + "retrieved\n", + "\n", + "\n", + "\n", + "N26\n", + "\n", + "RemoteData (26)\n", + "@localhost\n", + "\n", + "\n", + "\n", + "N25->N26\n", + "\n", + "\n", + "CREATE\n", + "remote_folder\n", + "\n", + "\n", + "\n", + "N1\n", + "\n", + "Code (1)\n", + "mock_crystal17@localhost\n", + "\n", + "\n", + "\n", + "N1->N25\n", + "\n", + "\n", + "INPUT_CALC\n", + "code\n", + "\n", + "\n", + "\n", + "N24\n", + "\n", + "KindData (24)\n", + "\n", + "\n", + "\n", + "N24->N25\n", + "\n", + "\n", + "INPUT_CALC\n", + "kinds\n", + "\n", + "\n", + "\n", + "N22\n", + "\n", + "SymmetryData (22)\n", + "hall_number: 400\n", + "symmops: 16\n", + "\n", + "\n", + "\n", + "N22->N25\n", + "\n", + "\n", + "INPUT_CALC\n", + "symmetry\n", + "\n", + "\n", + "\n", + "N20\n", + "\n", + "StructureData (20)\n", + "Ni2O2\n", + "\n", + "\n", + "\n", + "N20->N25\n", + "\n", + "\n", + "INPUT_CALC\n", + "structure\n", + "\n", + "\n", + "\n", + "N23\n", + "\n", + "CryInputParamsData (23)\n", + "\n", + "\n", + "\n", + "N23->N25\n", + "\n", + "\n", + "INPUT_CALC\n", + "parameters\n", + "\n", + "\n", + "\n", + "N4\n", + "\n", + "BasisSetData (4)\n", + "\n", + "\n", + "\n", + "N4->N25\n", + "\n", + "\n", + "INPUT_CALC\n", + "basissets__O\n", + "\n", + "\n", + "\n", + "N3\n", + "\n", + "BasisSetData (3)\n", + "\n", + "\n", + "\n", + "N3->N25\n", + "\n", + "\n", + "INPUT_CALC\n", + "basissets__Ni\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph = Graph(graph_attr={'size': \"6,8!\", \"rankdir\": \"LR\"})\n", + "graph.add_node(calcnode)\n", + "graph.add_incoming(calcnode, annotate_links=\"both\")\n", + "graph.add_outgoing(calcnode, annotate_links=\"both\")\n", + "graph.graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `retrieved` `FolderData` output node contains the CRYSTAL17 main input and output file." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "tags": [ + "nbreg_compare_output" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['_scheduler-stderr.txt', '_scheduler-stdout.txt', 'fort.34', 'main.out']" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "calcnode.outputs.retrieved.list_object_names()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `results` `Dict` output node contains key values extracted from the CRYSTAL17 standard output file." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```json\n", + "{\n", + " \"units\": {\n", + " \"angle\": \"degrees\",\n", + " \"energy\": \"eV\",\n", + " \"length\": \"angstrom\",\n", + " \"conversion\": \"CODATA2014\"\n", + " },\n", + " \"energy\": -85124.893667339,\n", + " \"errors\": [],\n", + " \"header\": {\n", + " \"crystal_version\": 17,\n", + " \"crystal_subversion\": \"1.0.1\"\n", + " },\n", + " \"volume\": 36.099581472,\n", + " \"warnings\": [],\n", + " \"calculation\": {\n", + " \"n_ao\": 46,\n", + " \"spin\": true,\n", + " \"type\": \"unrestricted open shell\",\n", + " \"n_atoms\": 4,\n", + " \"n_shells\": 14,\n", + " \"n_symops\": 16,\n", + " \"n_core_el\": 40,\n", + " \"n_electrons\": 72,\n", + " \"n_kpoints_ibz\": 75,\n", + " \"n_kpoints_gilat\": 75\n", + " },\n", + " \"energy_units\": \"eV\",\n", + " \"parser_class\": \"CryMainParser\",\n", + " \"parser_errors\": [],\n", + " \"mulliken_spins\": [\n", + " 3.057,\n", + " -3.057,\n", + " -0.072,\n", + " 0.072\n", + " ],\n", + " \"parser_version\": \"0.11.0\",\n", + " \"scf_iterations\": 13,\n", + " \"number_of_atoms\": 4,\n", + " \"parser_warnings\": [],\n", + " \"mulliken_charges\": [\n", + " 0.398,\n", + " 0.397,\n", + " -0.398,\n", + " -0.397\n", + " ],\n", + " \"parser_exceptions\": [],\n", + " \"mulliken_electrons\": [\n", + " 27.602,\n", + " 27.603,\n", + " 8.398,\n", + " 8.397\n", + " ],\n", + " \"mulliken_spin_total\": 0.0,\n", + " \"number_of_assymetric\": 4,\n", + " \"execution_time_seconds\": 187\n", + "}\n", + "```" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display_json(calcnode.outputs.results.get_dict())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To understand the format of the parsed data,\n", + "the raw parser is exposed on the command line as `verdi data crystal17.parse stdout`,\n", + "which can be used to parse existing output files." + ] + } + ], + "metadata": { + "blogpost": true, + "author": "Chris Sewell", + "date": "2019-11-01", + "tags": "QM-20.0.3,aiida-1.1", + "jupytext": { + "formats": "ipynb,md:myst" + }, + "kernelspec": { + "display_name": "Python 3", + "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.6.7" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "256px" + }, + "toc_section_display": true, + "toc_window_display": true + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/stories/browse_discover.md b/docs/stories/browse_discover.md new file mode 100644 index 0000000..b2d7248 --- /dev/null +++ b/docs/stories/browse_discover.md @@ -0,0 +1,264 @@ +--- +blogpost: true +author: Giovanni Pizzi, Francisco Ramirez +date: 2020-08-01 +tags: QM-20.03.0,aiida-1.3 +--- + +# Browsing a curated database + +A common source of problems for new users of AiiDA often arises when they receive an exported database and want to get an idea of its content but don't know how. + +In the following section we will use the database from ["Two-dimensional materials from high-throughput computational exfoliation of experimentally known compounds"](https://archive.materialscloud.org/record/2017.0008/v3). + +The final goal will be to create a table of the value of the band gap for a set of 2D materials from this study, filtering only those where the band gap is below a given value. + +We will show you here, step by step, how to start from a new unknown database, understand its layout, and eventually prepare the final query. +This will give you some guidance on how to apply and extend queries for your own needs. + +This story can be run in the [Quantum Mobile 20.03.0](https://github.com/marvel-nccr/quantum-mobile/releases/tag/20.03.0) virtual machine, that you need to download and install first (unless you have AiiDA already installed on your computer; we are using AiiDA 1.3 here). +You will also need to download and import the [2D materials database](https://archive.materialscloud.org/record/file?filename=two_dimensional_database.aiida&file_id=d1f3ac29-e3b0-400b-8109-8455be66160b&record_id=18) from [the corresponding Materials Cloud Archive entry](https://archive.materialscloud.org/record/2017.0008/v3) +This can be easily done by running the following commands in your AiiDA environment: + +```bash +(aiida) max@qmobile:~$ wget 'https://archive.materialscloud.org/record/file?file_id=d1f3ac29-e3b0-400b-8109-8455be66160b&filename=two_dimensional_database.aiida&record_id=18' -O 2D_database.aiida +(aiida) max@qmobile:~$ verdi import 2D_database.aiida +``` + +## Using the discover section + +In the case of this particular database, an interface that allows easy exploration of curated results is available through the [corresponding Discover section](https://www.materialscloud.org/discover/2dstructures/) of Materials Cloud. +This interface will provide a general overview of all materials available, and choosing any of them (let's say, [silver bromide](https://www.materialscloud.org/discover/2dstructures/details/AgBr)) will give you access to its available calculated properties, as well to information on the provenance of such properties. + +In this story, we are interested in the band gap of a material. You can then use the Explore button (the little AiiDA icon) next to the text ``Band gap [eV]: 1.3`` to go to the [actual node](https://www.materialscloud.org/explore/2dstructures/details/89315c33-2f9b-41ab-b7d4-22aff0ae75f4) that contains this value. +Similarly, you can click on the Explore button next to the band structure plot to inspect the ``BandsData`` node, and so on. +We invite you to browse the Discover and Explore sections for this database on you own, to get an idea of the features offered by the Materials Cloud interface. + +For our purposes, now we will just take note of the UUID of the ``Dict`` node containing the value of the band gap (``89315c33-2f9b-41ab-b7d4-22aff0ae75f4``) and we will see together how to check all information in the local database that you have imported earlier. + +## Manually browsing the database + +Now that we have a local instance of this database, we can see how to get some information regarding our node of interest (identified previously as the one that contains the band gap energy of AgBr). +For this we will use the AiiDA interactive shell: + +```bash +(aiida) max@qmobile:~$ verdi shell +``` + +We start by performing some browsing using the AiiDA API to explore the properties and connections of this node. + +1. Load the node by using its UUID (note that the integer identifiers, called PKs, will most probably be different in your DB, but the UUIDs will always be the same): + +```ipython +In [1]: bandgap_node = load_node('89315c33-2f9b-41ab-b7d4-22aff0ae75f4') +In [2]: bandgap_node +Out[2]: +``` + +2. One can check the attributes and discover that the band gap is in eV and is stored in the attribute named ``band_gap`` (knowing that the parser always returns eV, we are not going to use the units in the following, but one could generalise the query later if necessary). + +```ipython +In [3]: bandgap_node.attributes +Out[3]: {'band_gap': 1.25790023795923, 'band_gap_units': 'eV', 'is_insulator': True} +``` + +3. We start inspecting the provenance using the ``.creator`` method to get the calculation that generated the ``Dict`` data node: + +```ipython +In [4]: calculation_node = bandgap_node.creator +In [5]: calculation_node +Out[5]: +``` + +4. Then we can use ``.inputs.LABEL_NAME`` to access any of the inputs of this calculation. In our case, we will be interested in the one labeled ``bands`` (note that you can use tab completion to discover all input link labels: after ``.inputs.`` press the TAB key twice to see all available labels): + +```ipython +In [6]: bands_node = calculation_node.inputs.bands +In [7]: bands_node +Out[7]: +``` + +5. In the same way we did before, we can now check the calculation that created this ``BandsData`` node (i.e., the band structure), to discover that it was a Quantum ESPRESSO run: + +```ipython +In [8]: qecalc_node = bands_node.creator +In [9]: qecalc_node +Out[9]: +``` + +6. Finally, we can check another input one level up to find the original crystal structure: + +```ipython +In [10]: qecalc_node.inputs.structure +Out[10]: +``` + +Note that we don't really need all of the intermediate node variables. +We did it mostly for convenience, but all of these steps can just be concatenated in a single long chain, as follows: + +```ipython +In [11]: bandgap_node.creator.inputs.bands.creator.inputs.structure +Out[11]: +``` + +The advantage of the long string above is that after every dot you can use tab completion, and therefore (once you get used to it) it becomes very quick to browse advanced provenance graphs in the verdi shell. + +One more thing one might want to do is to check if there is a better way to distinguish the ``CalcFunctionNode`` that I got at ``Out[5]`` above (stored in ``calculation_node = bandgap_node.creator``). +Let us check its attributes: + +```ipython +In [12]: bandgap_node.creator.attributes.keys() +Out[12]: dict_keys(['function_name', 'sealed', 'first_line_source_code', 'namespace', 'source_code', 'source_file']) +In [13]: bandgap_node.creator.attributes['function_name'] +Out[13]: 'get_bandgap_inline' +``` + +This information will be useful in the following section, and will basically allow us to perform a query by filtering by the original function name. + +Now, after all these steps, we have a better understanding of the the structure of the data. +Another useful tool to get a good idea of the connectivity is the graph generator. +One can use ``verdi node graph generate`` to visualize the provenance surrounding a node. +Limiting it to four levels up (ancestors) will be enough for this case; we will also avoid to show any descendants, since we are not interested here in calculations that used the data node as an input. +Note that this command has to be executed outside of the verdi shell. + +```bash +(aiida) max@qmobile:~$ verdi node graph generate --process-in --process-out --ancestor-depth=4 --descendant-depth=0 89315c33 +``` + +The result should look something like this: + +![provenance](../images/provenance-1.png) + +## Systematic querying of the database + +Now that we have understood broadly the data layout (provenance links between nodes, their types, and some of the relevant attributes keys and values), let's now construct a query using the QueryBuilder in order to get the band structure of a set of 2D materials. +Create a new text file and copy the content below (these are essentially python scripts, so you can use the `.py` extension). +There are some comments which explain the purpose of each line of code, please read them carefully to understand how the query is constructed. +If you are not familiar with the ``QueryBuilder``, we refer to the `official AiiDA documentation for more details `_. + +```python +from aiida.orm import QueryBuilder, Dict, CalculationNode, BandsData, StructureData + +# Create a new query builder object +query = QueryBuilder() + +# I want, in the end, the 'band_gap' property returned ("projected") +# This is in the attributes of the Dict node +# As an additional challenge, I also want to filter them and get only those where the band gap (in eV) is < 0.5 +query.append( + Dict, + project=['attributes.band_gap'], + filters={'attributes.band_gap': {'<': 0.5}}, + tag='bandgap_node' +) + +# This node must have been generated by a CalcFunctionNode (so, with outgoing link the node of the previously +# part, that we tagged as `bandgap_node`, and I only want those where the +# function name stored in the attributes is 'get_bandgap_inline' +query.append( + CalcFunctionNode, + filters={'attributes.function_name': 'get_bandgap_inline'}, + with_outgoing='bandgap_node', + tag='bandgap_calc' +) + +# One of the inputs should be a BandsData (band structure node in AiiDA) +query.append(BandsData, with_outgoing='bandgap_calc', tag='band_structure') + +# This should have been computed by a calculation (we know it's always Quantum ESPRESSO +# in this specific DB, so I don't add more specific filters, but I could if I wanted to) +query.append(CalculationNode, with_outgoing='band_structure', tag='qe') + +# I want to get back the input crystal structure, and I want to get back +# the whole AiiDA node (indicated with '*') rather than just some attributes +query.append(StructureData, with_outgoing='qe', project='*') + +# So, now, summarizing, I have decided to project on two things: the band_gap and the structure node. +# I iterate on the query results, and I will get the two values for each matching result. +for band_gap, structure in query.all(): + print("Band gap for {}: {:.3f} eV".format(structure.get_formula(), band_gap)) +``` + +With these few lines of code (essentially 8, removing the comments, the indentation, and the import line) one is able to perform a query that will return all the structures (and band gaps) that are below a 0.5 eV threshold. +You can now execute the script by running ``verdi run ``. +Here is the output you should obtain if you only have the 2D materials database in your profile. + +```bash +Band gap for I4Zr2: 0.416 eV +Band gap for Br2Nd2O2: 0.308 eV +Band gap for Br2Cr2O2: 0.448 eV +Band gap for Br4O2V2: 0.108 eV +Band gap for Cl2La2: 0.003 eV +Band gap for Cl2Co: 0.029 eV +Band gap for CdClO: 0.217 eV +Band gap for Cl2Er2S2: 0.252 eV +Band gap for Cl4O2V2: 0.010 eV +Band gap for CdClO: 0.251 eV +Band gap for GeI2La2: 0.369 eV +Band gap for Se2Zr: 0.497 eV +Band gap for Cu4Te2: 0.207 eV +Band gap for Br2Cr2S2: 0.441 eV +Band gap for Co2H4O4: 0.014 eV +Band gap for Cl2Er2S2: 0.252 eV +Band gap for Br2Co: 0.039 eV +Band gap for I2Ni: 0.295 eV +Band gap for I2N2Ti2: 0.020 eV +Band gap for Cl2Cu: 0.112 eV +Band gap for Cl2O2Yb2: 0.006 eV +Band gap for Cl2O2Yb2: 0.006 eV +Band gap for Br2Co: 0.196 eV +Band gap for C2: 0.000 eV +Band gap for Cl2La2: 0.008 eV +Band gap for Br2Nd2O2: 0.002 eV +Band gap for I2O2Pr2: 0.030 eV +Band gap for Cl2Co: 0.171 eV +Band gap for Cl2Cu: 0.158 eV +Band gap for Cl2Er2S2: 0.203 eV +Band gap for Br2Cr2S2: 0.427 eV +Band gap for S2Ti: 0.059 eV +Band gap for Br2Cr2O2: 0.486 eV +Band gap for I2Ni: 0.319 eV +``` + +Now that you have learnt how to create such a query, you can have more fun adding additional statements before calling ``.all()``. +Here a couple of examples: + +- You can check that the input code of the `qe` calculation was indeed a using the Quantum ESPRESSO plugin (``quantumespresso.pw``): + + ```python + query.append(Code, with_outgoing='qe', filters={'attributes.input_plugin': 'quantumespresso.pw'}) + ``` + +- You can project back also the total running time (wall time) of the Quantum ESPRESSO calculation (it is in an output node with link label ``output_parameters``). + For this you needs to add a third element to the tuple when looping over ``.all()``: + + ```python + query.append(Dict, with_incoming='qe', edge_filters={'label':'output_parameters'}, project=['attributes.wall_time_seconds']) + + (...) + + for band_gap, structure, walltime in query.all(): + print("Band gap for {}: {:.3f} eV (walltime = {}s)".format(structure.get_formula(), band_gap, walltime)) + ``` + +## Behind the scenes + +As a final comment, we strongly suggest using the QueryBuilder rather than going directly into the SQL database, even if you know SQL. +We have spent significant efforts in making the QueryBuilder interface easy to use, and taking care ourselves of converting this into the corresponding SQL. +Just for reference, if you do ``print(query)`` you get the corresponding SQL statement for the query above, that should translate to the following not-so-short string: + +```sql +SELECT db_dbnode_1.attributes #> '{band_gap}' AS anon_1, db_dbnode_2.uuid, db_dbnode_2.attributes, db_dbnode_2.id, db_dbnode_2.extras, db_dbnode_2.label, db_dbnode_2.mtime, db_dbnode_2.ctime, db_dbnode_2.node_type, db_dbnode_2.process_type, db_dbnode_2.description, db_dbnode_2.user_id, db_dbnode_2.dbcomputer_id +FROM db_dbnode AS db_dbnode_1 JOIN db_dblink AS db_dblink_1 ON db_dblink_1.output_id = db_dbnode_1.id JOIN db_dbnode AS db_dbnode_3 ON db_dblink_1.input_id = db_dbnode_3.id JOIN db_dblink AS db_dblink_2 ON db_dblink_2.output_id = db_dbnode_3.id JOIN db_dbnode AS db_dbnode_4 ON db_dblink_2.input_id = db_dbnode_4.id JOIN db_dblink AS db_dblink_3 ON db_dblink_3.output_id = db_dbnode_4.id JOIN db_dbnode AS db_dbnode_5 ON db_dblink_3.input_id = db_dbnode_5.id JOIN db_dblink AS db_dblink_4 ON db_dblink_4.output_id = db_dbnode_5.id JOIN db_dbnode AS db_dbnode_2 ON db_dblink_4.input_id = db_dbnode_2.id +WHERE CAST(db_dbnode_5.node_type AS VARCHAR) LIKE 'process.calculation.%%' AND CAST(db_dbnode_4.node_type AS VARCHAR) LIKE 'data.array.bands.%%' AND CAST(db_dbnode_2.node_type AS VARCHAR) LIKE 'data.structure.%%' AND CAST(db_dbnode_1.node_type AS VARCHAR) LIKE 'data.dict.%%' AND CASE WHEN (jsonb_typeof(db_dbnode_1.attributes #> %(attributes_1)s) = 'number') THEN CAST((db_dbnode_1.attributes #>> '{band_gap}') AS FLOAT) < 0.5 ELSE false END AND CAST(db_dbnode_3.node_type AS VARCHAR) LIKE 'process.calculation.%%' AND CASE WHEN (jsonb_typeof(db_dbnode_3.attributes #> %(attributes_2)s) = 'string') THEN (db_dbnode_3.attributes #>> '{function_name}') = 'get_bandgap_inline' ELSE false END +``` + +So unless you feel ready to tackle this, we suggest that you stick with the simpler QueryBuilder interface! + +Have fun with AiiDA! + +1. https://aiida.readthedocs.io/projects/aiida-core/en/latest/querying/querybuilder/queryhelp.html +2. https://aiida.readthedocs.io/projects/aiida-core/en/v1.2.0/querying/querybuilder/queryhelp.html +3. https://aiida.readthedocs.io/projects/aiida-core/en/latest +4. https://github.com/aiidateam/aiida-core/wiki/Writing-documentation +5. https://aiida.readthedocs.io/projects/aiida-core/en/latest/howto/data.html#finding-and-querying-for-data diff --git a/docs/stories/index.md b/docs/stories/index.md new file mode 100644 index 0000000..1e861b1 --- /dev/null +++ b/docs/stories/index.md @@ -0,0 +1,4 @@ +# AiiDA user stories + +This page is auto-populated by ablog + diff --git a/docs/stories/visualising_graphs.md b/docs/stories/visualising_graphs.md new file mode 100644 index 0000000..ecbd3d3 --- /dev/null +++ b/docs/stories/visualising_graphs.md @@ -0,0 +1,175 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.12 + jupytext_version: 1.6.0 +kernelspec: + display_name: Python 3 + language: python + name: python3 +blogpost: true +author: Chris Sewell +date: "2019-12-01" +tags: "QM-19.0.1,aiida-1.1" +--- + +# How to visualize provenance + ++++ + +The provenance graph of a database can be visually inspected, *via* [graphviz](https://www.graphviz.org/), using both the python API and command-line interface. + +:::{note} +This tutorial can be downloaded and run as a Jupyter Notebook: {download}`visualising_graphs.ipynb` +::: + +:::{seealso} +`verdi graph generate -h` +::: + +We first load the database and required modules: + +```{code-cell} ipython3 +:init_cell: true + +from aiida import load_profile +profile = load_profile() +``` + +```{code-cell} ipython3 +:init_cell: true + +from aiida.common import LinkType +from aiida.orm.utils.links import LinkPair +from aiida.tools.visualization import Graph, pstate_node_styles +``` + +The example provenance graph, used in this tutorial, can be downloaded {download}`from this link <../archives/visualising_graphs.aiida>` + +It can then be imported into the database: + +```{code-cell} ipython3 +:tags: [remove-stdout,remove-stderr] + +!verdi import -n ../archives/visualising_graphs.aiida +``` + +```{code-cell} ipython3 +dict1_uuid = '0ea79a16-501f-408a-8c84-a2704a778e4b' +calc1_uuid = 'b23e692e-4e01-48dd-b515-4c63877d73a4' +``` + +The {py:class}`~aiida.tools.visualization.graph.Graph` class is used to store visual representations of the nodes and edges, which can be added separately or cumulatively by one of the graph traversal methods. +The {py:attr}`~aiida.tools.visualization.graph.Graph.graphviz` attribute returns a [graphviz.Digraph](https://graphviz.readthedocs.io/en/stable/) instance, which will auto-magically render the graph in the notebook, or can be used to save the graph to file. + +```{code-cell} ipython3 +graph = Graph() +graph.add_node(dict1_uuid) +graph.add_node(calc1_uuid) +graph.graphviz +``` + +```{code-cell} ipython3 +graph.add_edge( + dict1_uuid, calc1_uuid, + link_pair=LinkPair(LinkType.INPUT_CALC, "input1")) +graph.graphviz +``` + +```{code-cell} ipython3 +graph.add_incoming(calc1_uuid) +graph.add_outgoing(calc1_uuid) +graph.graphviz +``` + +The {py:class}`~aiida.tools.visualization.graph.Graph` can also be initialized with global style attributes, +as outlined in the [graphviz attributes table](https://www.graphviz.org/doc/info/attrs.html). + +```{code-cell} ipython3 +graph = Graph(node_id_type="uuid", + global_node_style={"penwidth": 1}, + global_edge_style={"color": "blue"}, + graph_attr={"size": "8!,8!", "rankdir": "LR"}) +graph.add_incoming(calc1_uuid) +graph.add_outgoing(calc1_uuid) +graph.graphviz +``` + +Additionally functions can be parsed to the {py:class}`~aiida.tools.visualization.graph.Graph` initializer, to specify exactly how each node will be represented. For example, the {py:func}`~aiida.tools.visualization.graph.pstate_node_styles` function colors process nodes by their process state. + +```{code-cell} ipython3 +def link_style(link_pair, **kwargs): + return {"color": "blue"} + +graph = Graph(node_style_fn=pstate_node_styles, + link_style_fn=link_style, + graph_attr={"size": "8!,8!", "rankdir": "LR"}) +graph.add_incoming(calc1_uuid) +graph.add_outgoing(calc1_uuid) +graph.graphviz +``` + +Edges can be annotated by one or both of their edge label and link type. + +```{code-cell} ipython3 +graph = Graph(graph_attr={"size": "8!,8!", "rankdir": "LR"}) +graph.add_incoming(calc1_uuid, + annotate_links="both") +graph.add_outgoing(calc1_uuid, + annotate_links="both") +graph.graphviz +``` + +The {py:meth}`~aiida.tools.visualization.graph.Graph.recurse_descendants` and {py:meth}`~aiida.tools.visualization.graph.Graph.recurse_ancestors` methods can be used to construct a full provenance graph. + +```{code-cell} ipython3 +graph = Graph(graph_attr={"size": "8!,8!", "rankdir": "LR"}) +graph.recurse_descendants( + dict1_uuid, + origin_style=None, + include_process_inputs=True, + annotate_links="both" +) +graph.graphviz +``` + +The link types can also be filtered, to view only the 'data' or 'logical' provenance. + +```{code-cell} ipython3 +graph = Graph(graph_attr={"size": "8,8!", "rankdir": "LR"}) +graph.recurse_descendants( + dict1_uuid, + origin_style=None, + include_process_inputs=True, + annotate_links="both", + link_types=("input_calc", "create") +) +graph.graphviz +``` + +```{code-cell} ipython3 +graph = Graph(graph_attr={"size": "8,8!", "rankdir": "LR"}) +graph.recurse_descendants( + dict1_uuid, + origin_style=None, + include_process_inputs=True, + annotate_links="both", + link_types=("input_work", "return") +) +graph.graphviz +``` + +If you wish to highlight specific node classes, +then the `highlight_classes` option can be used +to only color specified nodes: + +```{code-cell} ipython3 +graph = Graph(graph_attr={"size": "20,20", "rankdir": "LR"}) +graph.recurse_descendants( + dict1_uuid, + highlight_classes=['Dict'] +) +graph.graphviz +``` diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..8f1c09c --- /dev/null +++ b/environment.yml @@ -0,0 +1,13 @@ +name: readthedocs +dependencies: + - python=3.7 + - pip + - postgresql + - pip: + - myst-nb~=0.10.1 + - sphinx-book-theme==0.0.39b1 + - sphinx-panels~=0.5.2 + - ablog + - sphinx-autobuild + - aiida-core==1.4.2 + - pgtest diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..8e7a3a3 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,7 @@ +myst-nb~=0.10.1 +sphinx-book-theme==0.0.39b1 +sphinx-panels~=0.5.2 +ablog +sphinx-autobuild +aiida-core==1.4.2 +pgtest diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..f39bc20 --- /dev/null +++ b/tox.ini @@ -0,0 +1,24 @@ +# configuration to run via tox + +[tox] +envlist = docs-clean + +[testenv] +skip_install = true + +[testenv:docs-{update,clean}] +description = Build the documentation +deps = -rrequirements-docs.txt +whitelist_externals = rm +commands = + clean: rm -rf docs/_build + sphinx-build -nW --keep-going -b {posargs:html} docs/ docs/_build/{posargs:html} + +[testenv:docs-live] +description = Build the documentation and launch browser +deps = -rrequirements-docs.txt +commands = + sphinx-autobuild \ + --re-ignore _build/.* \ + --port 0 --open-browser \ + -n -b {posargs:html} docs/ docs/_build/{posargs:html}