From 4e6694312ac92118285bd535503c5f90a833dc14 Mon Sep 17 00:00:00 2001 From: ramirezfranciscof Date: Mon, 10 Aug 2020 17:16:50 +0200 Subject: [PATCH 01/20] Add story for exploring a database and querying --- Makefile | 20 ++ README.md | 5 + source/conf.py | 52 +++++ source/images/provenance-1.png | Bin 0 -> 138931 bytes source/index.rst | 21 ++ source/stories/browse_discover.rst | 299 +++++++++++++++++++++++++++++ source/stories/index.rst | 12 ++ 7 files changed, 409 insertions(+) create mode 100644 Makefile create mode 100644 source/conf.py create mode 100644 source/images/provenance-1.png create mode 100644 source/index.rst create mode 100644 source/stories/browse_discover.rst create mode 100644 source/stories/index.rst 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..6806beb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # aiida-singledocs Single page or other short miscelaneous documentation + + +### To Do: + +* Autoinstall sphinx... diff --git a/source/conf.py b/source/conf.py new file mode 100644 index 0000000..0fd5aa2 --- /dev/null +++ b/source/conf.py @@ -0,0 +1,52 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'aiida-singledocs' +copyright = '2020, Francisco Ramirez' +author = 'Francisco Ramirez' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# 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 = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] \ No newline at end of file diff --git a/source/images/provenance-1.png b/source/images/provenance-1.png new file mode 100644 index 0000000000000000000000000000000000000000..74ea721e081b8c0c93febb6be2215f4456c2a9c2 GIT binary patch literal 138931 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 Om!tiW12y{t&i@T@)-1gM literal 0 HcmV?d00001 diff --git a/source/index.rst b/source/index.rst new file mode 100644 index 0000000..b32d957 --- /dev/null +++ b/source/index.rst @@ -0,0 +1,21 @@ +.. aiida-singledocs documentation master file, created by + sphinx-quickstart on Wed Aug 5 16:36:35 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to aiida-singledocs's documentation! +============================================ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + Stories <./stories/index> + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/source/stories/browse_discover.rst b/source/stories/browse_discover.rst new file mode 100644 index 0000000..72543be --- /dev/null +++ b/source/stories/browse_discover.rst @@ -0,0 +1,299 @@ +.. _stories:browse_export: + +*************************** +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" to show some examples of useful queries that can help with this. +This will hopefully give you some guidance on how to apply and extend queries for your needs + +Using the discover section +.......................... + +In the case of this particular database, an easy to explore interface is available through the `specific Discover section `_ of the Materials Cloud. +This interface will provide a general overview of all materials available, and choosing any of them (let's say, `silver bromide `_) will give you access to of its available calculated properties. +If one was interested in, say, the band structure properties, one could use the explore button next to the `Band gap [eV]: 1.3` to go to the `actual node `_ that contains this information. + +For our purposes now we will just take note of the UUID of this node and see how to check the information in a local database. +We invite the reader to browse the discovery and explore sections for this database on their own to get an idea of the features offered by the Materials Cloud interface. + + +Setting up the work environment +............................... + +If you already have an AiiDA setup you want to use to follow this instructions, all you need to do is download the 2D database from `its archive entry `_ (or `here `_ is a direct download link) and run ``verdi import ``. +We would anyways recommend that you create a new profile (see instructions below) so that the data does not get mixed up with the one from your production environment. + +If you don't already have AiiDA installed or you prefer to use a more conteinerized environment, you can download the `Quantum Mobile `_, which is an already setted up virtual machine. +You can click on the download button and follow the instructions in the github releases section. +To get the database into your virtual machine by either setting up a `shared folder `_ or by directly downloading the file there by running: + +.. code-block:: bash + + wget 'https://archive.materialscloud.org/record/file?file_id=d1f3ac29-e3b0-400b-8109-8455be66160b&filename=two_dimensional_database.aiida&record_id=18' -O two_dimensional_database.aiida + +Once you have your virtual machine up and running and before importing the database, you will need to set up a user profile. +We will do so by running the ``quicksetup`` command, and AiiDA will automatically create the new clean database to be used by it. +In the case below we chose to name the profile `querytest`, but you can use whatever name you prefer (just be consistent afterwards). +If you have not done so already, make sure you have loaded the correct working environment before going on with this procedure (see the first command line below). + +.. code-block:: bash + + max@qmobile:~$ workon aiida + (aiida) max@qmobile:~$ verdi quicksetup + Info: enter "?" for help + Info: enter "!" to ignore the default and set no value + Profile name [quicksetup]: querytest + User email [aiida@localhost]: + First name [Max]: + Last name [Scientist]: + Institution [Quantum Mobile]: + Warning: Found host 'localhost' but dropping '-h localhost' option for psql since this may cause psql to switch to password-based authentication. + Success: created new profile `querytest`. + Info: migrating the database. + Operations to perform: + Apply all migrations: auth, contenttypes, db + Running migrations: + Applying contenttypes.0001_initial... OK + Applying contenttypes.0002_remove_content_type_name... OK + Applying auth.0001_initial... OK + Applying auth.0002_alter_permission_name_max_length... OK + Applying auth.0003_alter_user_email_max_length... OK + (...) + Applying db.0042_prepare_schema_reset... OK + Applying db.0043_default_link_label... OK + Success: database migration completed. + (aiida) max@qmobile:~$ + +Now switch from your current profile to this newly created one by using ``verdi profile setdefault querytest``, import the 2D materials database with ``verdi import two_dimensional_database.aiida``, and now we are ready to begin. + +.. figure:: ../images/provenance-1.png + +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: + +.. code-block:: bash + + (aiida) max@qmobile:~$ verdi shell + +Now we will do some manual querying to explore this node properties and connections. + +1. Load the node by using its UUID: + +.. code-block:: Python + + 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 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 it was necessary). + +.. code-block:: Python + + 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 .creator to get the calculation that generated the data node: + +.. code-block:: Python + + 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 there is tab completion after .inputs.): + +.. code-block:: Python + + In [6]: bands_node = calculation_node.inputs.bands_node + 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 (band structure), and see it was a Quantum ESPRESSO run: + +.. code-block:: Python + + 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: + +.. code-block:: Python + + In [10]: qecalc_node.inputs.structure + Out[10]: + + +Note that we don't really need all of the intermediate node variables, as all of these steps can just be concatenated in a single chain of propery accessess from our originally identified ``bandgap_node``. +In the end, we wil arrive at the exact same structure node: + +.. code-block:: Python + + In [11]: bandgap_node.creator.inputs.bands.creator.inputs.structure + Out[11]: + + +One more 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’s check its attributes: + +.. code-block:: Python + + 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 to filter by the function name. + +Now after all of this 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 4 levels up(will be enough for this case). +Not that this has to be executed outside of the verdi shell. + +.. code-block:: 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: + + +Systematic querying of the database +................................... + + +Let’s now construct the query using the QueryBuilder. +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 of the lines of code. + +.. code-block:: 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 + # 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 has to be generated by a CalculationNode (it's a super class of CalcFunctionNode, + # one could write CalcFunctionNode as well), and I only want those where the + # function name stored in the attributes is 'get_bandgap_inline' + query.append( + CalculationNode, + 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 Quantum ESPRESSO + # in this DB so I don't add more specific filters) + 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 AiiDA node (indicated with '*') + query.append(StructureData, with_outgoing='qe', project='*') + + # I have decided to project on two things: the band_gap and the structure node + for band_gap, structure in query.all(): + print("Band gap for {}: {:.3f} eV".format(structure.get_formula(), band_gap)) + + +With these 8 lines of code (removing the comments 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 treshold. +You can 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. + +.. code-block:: 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 + +One can also have fun adding more statements before calling `.all()`. +Here a couple of examples: + +- One can check that the input code of AiiDA was a specific one: + +.. code-block:: python + + query.append(Code, with_outgoing='qe', filters={'attributes.input_plugin': 'quantumespresso.pw'}) + + +- One can project back the total running time (wall time) of the Quantum ESPRESSO calculation (it's in an output node with link label 'output_parameters'). For this one needs to add a third element to the tuple when looping over .all(): + +.. code-block:: 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 = {})".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 PSQL DB. +We’ve 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: + +.. code-block:: 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, I’d rather stick with the simpler QueryBuilder interface! + +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/source/stories/index.rst b/source/stories/index.rst new file mode 100644 index 0000000..989f21e --- /dev/null +++ b/source/stories/index.rst @@ -0,0 +1,12 @@ +.. _stories: + +Where does this text appear? + +List of stories +--------------- + +.. toctree:: + :maxdepth: 1 + :numbered: + + ./browse_discover From 95c80a76dbf75f1ff89c11a0d91bd734adf7cce8 Mon Sep 17 00:00:00 2001 From: ramirezfranciscof Date: Thu, 27 Aug 2020 12:03:49 +0200 Subject: [PATCH 02/20] Intermediate data save --- source/stories/browse_discover.rst | 58 ++-------------------- source/stories/index.rst | 31 ++++++++++-- source/stories/setups.rst | 77 ++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 56 deletions(-) create mode 100644 source/stories/setups.rst diff --git a/source/stories/browse_discover.rst b/source/stories/browse_discover.rst index 72543be..8f90f72 100644 --- a/source/stories/browse_discover.rst +++ b/source/stories/browse_discover.rst @@ -6,9 +6,12 @@ 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" to show some examples of useful queries that can help with this. +In the following section we will use the database from `"Two-dimensional materials from high-throughput computational exfoliation of experimentally known compounds" `_ to show some examples of useful queries that can help with this. This will hopefully give you some guidance on how to apply and extend queries for your needs +* Virtual Machine: `QM 20.03.0 `_ +* Databases (direct download link): `2D materials `_ + Using the discover section .......................... @@ -19,58 +22,6 @@ If one was interested in, say, the band structure properties, one could use the For our purposes now we will just take note of the UUID of this node and see how to check the information in a local database. We invite the reader to browse the discovery and explore sections for this database on their own to get an idea of the features offered by the Materials Cloud interface. - -Setting up the work environment -............................... - -If you already have an AiiDA setup you want to use to follow this instructions, all you need to do is download the 2D database from `its archive entry `_ (or `here `_ is a direct download link) and run ``verdi import ``. -We would anyways recommend that you create a new profile (see instructions below) so that the data does not get mixed up with the one from your production environment. - -If you don't already have AiiDA installed or you prefer to use a more conteinerized environment, you can download the `Quantum Mobile `_, which is an already setted up virtual machine. -You can click on the download button and follow the instructions in the github releases section. -To get the database into your virtual machine by either setting up a `shared folder `_ or by directly downloading the file there by running: - -.. code-block:: bash - - wget 'https://archive.materialscloud.org/record/file?file_id=d1f3ac29-e3b0-400b-8109-8455be66160b&filename=two_dimensional_database.aiida&record_id=18' -O two_dimensional_database.aiida - -Once you have your virtual machine up and running and before importing the database, you will need to set up a user profile. -We will do so by running the ``quicksetup`` command, and AiiDA will automatically create the new clean database to be used by it. -In the case below we chose to name the profile `querytest`, but you can use whatever name you prefer (just be consistent afterwards). -If you have not done so already, make sure you have loaded the correct working environment before going on with this procedure (see the first command line below). - -.. code-block:: bash - - max@qmobile:~$ workon aiida - (aiida) max@qmobile:~$ verdi quicksetup - Info: enter "?" for help - Info: enter "!" to ignore the default and set no value - Profile name [quicksetup]: querytest - User email [aiida@localhost]: - First name [Max]: - Last name [Scientist]: - Institution [Quantum Mobile]: - Warning: Found host 'localhost' but dropping '-h localhost' option for psql since this may cause psql to switch to password-based authentication. - Success: created new profile `querytest`. - Info: migrating the database. - Operations to perform: - Apply all migrations: auth, contenttypes, db - Running migrations: - Applying contenttypes.0001_initial... OK - Applying contenttypes.0002_remove_content_type_name... OK - Applying auth.0001_initial... OK - Applying auth.0002_alter_permission_name_max_length... OK - Applying auth.0003_alter_user_email_max_length... OK - (...) - Applying db.0042_prepare_schema_reset... OK - Applying db.0043_default_link_label... OK - Success: database migration completed. - (aiida) max@qmobile:~$ - -Now switch from your current profile to this newly created one by using ``verdi profile setdefault querytest``, import the 2D materials database with ``verdi import two_dimensional_database.aiida``, and now we are ready to begin. - -.. figure:: ../images/provenance-1.png - Manually browsing the database .............................. @@ -163,6 +114,7 @@ Not that this has to be executed outside of the verdi shell. The result should look something like this: +.. figure:: ../images/provenance-1.png Systematic querying of the database ................................... diff --git a/source/stories/index.rst b/source/stories/index.rst index 989f21e..a896b8a 100644 --- a/source/stories/index.rst +++ b/source/stories/index.rst @@ -1,9 +1,34 @@ .. _stories: -Where does this text appear? +AiiDA user stories +------------------ -List of stories ---------------- +This section contains a collection of stories inspired in real life 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 applyt the procedures in them as templates for when you need to perform similar tasks. + +If you intend to reproduce the procedures ilustrated in these stories, it is **strongly recommended** that you set up a work environment that is as similar as possible to the one used to test them. +The easiest way to ensure this is to follow the next 3 steps: + +1. Download the same `Quantum Mobile `_ version that was used to design/test the story. + If you already have that virtual machine, you can use the same. + +2. Initialize a new profile to start with a clean database (see the ``quicksetup`` command in the `AiiDA documentation `_). + You will want to use a different profile for each story so that you don't get any `noise` from the other databases nodes. + Remember to switch to this new profile by running ``verdi profile setdefault ``. + +3. Download all the necessary AiiDA databases in your virtual machine and import them into your new profile. + +It is **strongly recommended** that you first read the :ref:`Environment setup ` section, which will take you through the steps of setting up a clean work environment using a virtual machine. +This will allow you to have the same setup as the story, so that you can get the same results when using the same commands. +You can then start exploring variations from there, eventually being able to adapt the commands for your particular needs and for your specific database/version of the code. + +.. toctree:: + :maxdepth: 1 + :hidden: + + 0. Environment setups <./setups> + +Here is the list of all available user stories: .. toctree:: :maxdepth: 1 diff --git a/source/stories/setups.rst b/source/stories/setups.rst new file mode 100644 index 0000000..1c11453 --- /dev/null +++ b/source/stories/setups.rst @@ -0,0 +1,77 @@ +.. _stories:setups: + +******************************* +Setting up the work environment +******************************* + +All the AiiDA stories are designed and tested inside our virtual machines and with publicly available databases. +This allow users the possibility to set up the same work environment, and thus be able to obtain the same results when executing the described commands. + +At the begining of each story you will have instructions on how to set things up. +Some of these are may be story-specific steps, but there are 3 steps that +Each story uses a particular version of AiiDA and/or works with a different database: +but there is a common procedure to reproduce that environment. +You will need to: + +Installing the virtual machine +.............................. + +At the begining of each story, there will +You can find all virtual machines in the `Quantum Mobile website `_. +Just click on the download button (which will take you to the the `github releases section `_), search for the corresponding version, and follow the instructions. + + +Setting up a new profile +........................ + +Once you have your virtual machine up and running, it is also advisable that you set a different profile for each story. +This is because if you start mixing databases from different stories in the same profile, some of the commands may have different outputs and thus some procedures different behaviors. + +You can do this by running the ``quicksetup`` command, and AiiDA will automatically create a new clean database to be used by it. +In the case below we chose to name the profile `storytest`, but you can use whatever name you prefer (just be consistent afterwards). +If you have not done so already, remember to first load the python environment by running ``workon aiida``. + +.. code-block:: bash + + (aiida) max@qmobile:~$ verdi quicksetup + Info: enter "?" for help + Info: enter "!" to ignore the default and set no value + Profile name [quicksetup]: storytest + User email [aiida@localhost]: + First name [Max]: + Last name [Scientist]: + Institution [Quantum Mobile]: + Warning: Found host 'localhost' but dropping '-h localhost' option for psql since this may cause psql to switch to password-based authentication. + Success: created new profile `storytest`. + Info: migrating the database. + Operations to perform: + Apply all migrations: auth, contenttypes, db + Running migrations: + Applying contenttypes.0001_initial... OK + Applying contenttypes.0002_remove_content_type_name... OK + Applying auth.0001_initial... OK + Applying auth.0002_alter_permission_name_max_length... OK + Applying auth.0003_alter_user_email_max_length... OK + (...) + Applying db.0042_prepare_schema_reset... OK + Applying db.0043_default_link_label... OK + Success: database migration completed. + (aiida) max@qmobile:~$ + +Finally, you need to switch to this newly created profile by using ``verdi profile setdefault storytest`` before proceeding with any imports. + +Importing an AiiDA database +........................... + +Each story might use one or more AiiDA databases, indicated at the start of it. +These databases will tipically be found in the `Materials Cloud Archive `_, but other sources are also possible. +In any case, the download link should be indicated together with the database. + +You can either download the required databases and then move them into the virtual machine by using a `shared folder `_, or you can directly download them inside the virtual machine by using ``wget``. +For example, if you wanted to download the the `2D database `_, you would need to run: + +.. code-block:: bash + + wget 'https://archive.materialscloud.org/record/file?file_id=d1f3ac29-e3b0-400b-8109-8455be66160b&filename=two_dimensional_database.aiida&record_id=18' -O two_dimensional_database.aiida + +Where the link used in the command is the one of the ``.aiida`` file that can be found in the `Files` section of the `archive entry `_. From b09098b1bc89e2d4acfa30b706df921e21f5fbd9 Mon Sep 17 00:00:00 2001 From: ramirezfranciscof Date: Thu, 27 Aug 2020 15:21:48 +0200 Subject: [PATCH 03/20] First finished draft --- source/stories/browse_discover.rst | 12 +++-- source/stories/index.rst | 30 +++--------- source/stories/setups.rst | 77 ------------------------------ 3 files changed, 15 insertions(+), 104 deletions(-) delete mode 100644 source/stories/setups.rst diff --git a/source/stories/browse_discover.rst b/source/stories/browse_discover.rst index 8f90f72..cb3c0f0 100644 --- a/source/stories/browse_discover.rst +++ b/source/stories/browse_discover.rst @@ -7,10 +7,16 @@ 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" `_ to show some examples of useful queries that can help with this. -This will hopefully give you some guidance on how to apply and extend queries for your needs +This will hopefully give you some guidance on how to apply and extend queries for your own needs. -* Virtual Machine: `QM 20.03.0 `_ -* Databases (direct download link): `2D materials `_ +This story was designed using `Quantum Mobile 20.03.0 `_. +You will also need to download and import the `2D materials database `_. +This can be easily done by running the following commands in your aiida environment: + +.. code-block:: 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 .......................... diff --git a/source/stories/index.rst b/source/stories/index.rst index a896b8a..7f6d47f 100644 --- a/source/stories/index.rst +++ b/source/stories/index.rst @@ -3,32 +3,14 @@ AiiDA user stories ------------------ -This section contains a collection of stories inspired in real life 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 applyt the procedures in them as templates for when you need to perform similar tasks. +This section 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. -If you intend to reproduce the procedures ilustrated in these stories, it is **strongly recommended** that you set up a work environment that is as similar as possible to the one used to test them. -The easiest way to ensure this is to follow the next 3 steps: +Each story will specify at the begining which version of the `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 `_) ensures the reproducibility of the commands if you want to test them out yourself. +Any other further setups required (such as importing databases) will be described in the introduction of the story. -1. Download the same `Quantum Mobile `_ version that was used to design/test the story. - If you already have that virtual machine, you can use the same. - -2. Initialize a new profile to start with a clean database (see the ``quicksetup`` command in the `AiiDA documentation `_). - You will want to use a different profile for each story so that you don't get any `noise` from the other databases nodes. - Remember to switch to this new profile by running ``verdi profile setdefault ``. - -3. Download all the necessary AiiDA databases in your virtual machine and import them into your new profile. - -It is **strongly recommended** that you first read the :ref:`Environment setup ` section, which will take you through the steps of setting up a clean work environment using a virtual machine. -This will allow you to have the same setup as the story, so that you can get the same results when using the same commands. -You can then start exploring variations from there, eventually being able to adapt the commands for your particular needs and for your specific database/version of the code. - -.. toctree:: - :maxdepth: 1 - :hidden: - - 0. Environment setups <./setups> - -Here is the list of all available user stories: +Here is the list of currently available user stories: .. toctree:: :maxdepth: 1 diff --git a/source/stories/setups.rst b/source/stories/setups.rst deleted file mode 100644 index 1c11453..0000000 --- a/source/stories/setups.rst +++ /dev/null @@ -1,77 +0,0 @@ -.. _stories:setups: - -******************************* -Setting up the work environment -******************************* - -All the AiiDA stories are designed and tested inside our virtual machines and with publicly available databases. -This allow users the possibility to set up the same work environment, and thus be able to obtain the same results when executing the described commands. - -At the begining of each story you will have instructions on how to set things up. -Some of these are may be story-specific steps, but there are 3 steps that -Each story uses a particular version of AiiDA and/or works with a different database: -but there is a common procedure to reproduce that environment. -You will need to: - -Installing the virtual machine -.............................. - -At the begining of each story, there will -You can find all virtual machines in the `Quantum Mobile website `_. -Just click on the download button (which will take you to the the `github releases section `_), search for the corresponding version, and follow the instructions. - - -Setting up a new profile -........................ - -Once you have your virtual machine up and running, it is also advisable that you set a different profile for each story. -This is because if you start mixing databases from different stories in the same profile, some of the commands may have different outputs and thus some procedures different behaviors. - -You can do this by running the ``quicksetup`` command, and AiiDA will automatically create a new clean database to be used by it. -In the case below we chose to name the profile `storytest`, but you can use whatever name you prefer (just be consistent afterwards). -If you have not done so already, remember to first load the python environment by running ``workon aiida``. - -.. code-block:: bash - - (aiida) max@qmobile:~$ verdi quicksetup - Info: enter "?" for help - Info: enter "!" to ignore the default and set no value - Profile name [quicksetup]: storytest - User email [aiida@localhost]: - First name [Max]: - Last name [Scientist]: - Institution [Quantum Mobile]: - Warning: Found host 'localhost' but dropping '-h localhost' option for psql since this may cause psql to switch to password-based authentication. - Success: created new profile `storytest`. - Info: migrating the database. - Operations to perform: - Apply all migrations: auth, contenttypes, db - Running migrations: - Applying contenttypes.0001_initial... OK - Applying contenttypes.0002_remove_content_type_name... OK - Applying auth.0001_initial... OK - Applying auth.0002_alter_permission_name_max_length... OK - Applying auth.0003_alter_user_email_max_length... OK - (...) - Applying db.0042_prepare_schema_reset... OK - Applying db.0043_default_link_label... OK - Success: database migration completed. - (aiida) max@qmobile:~$ - -Finally, you need to switch to this newly created profile by using ``verdi profile setdefault storytest`` before proceeding with any imports. - -Importing an AiiDA database -........................... - -Each story might use one or more AiiDA databases, indicated at the start of it. -These databases will tipically be found in the `Materials Cloud Archive `_, but other sources are also possible. -In any case, the download link should be indicated together with the database. - -You can either download the required databases and then move them into the virtual machine by using a `shared folder `_, or you can directly download them inside the virtual machine by using ``wget``. -For example, if you wanted to download the the `2D database `_, you would need to run: - -.. code-block:: bash - - wget 'https://archive.materialscloud.org/record/file?file_id=d1f3ac29-e3b0-400b-8109-8455be66160b&filename=two_dimensional_database.aiida&record_id=18' -O two_dimensional_database.aiida - -Where the link used in the command is the one of the ``.aiida`` file that can be found in the `Files` section of the `archive entry `_. From 727559878e003c09e115c885a666b9303f68b177 Mon Sep 17 00:00:00 2001 From: Giovanni Pizzi Date: Mon, 31 Aug 2020 16:11:27 +0200 Subject: [PATCH 04/20] Giovanni's first review --- .gitignore | 2 + source/stories/browse_discover.rst | 117 ++++++++++++++++------------- 2 files changed, 68 insertions(+), 51 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af96791 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +build diff --git a/source/stories/browse_discover.rst b/source/stories/browse_discover.rst index cb3c0f0..46402fb 100644 --- a/source/stories/browse_discover.rst +++ b/source/stories/browse_discover.rst @@ -4,14 +4,18 @@ Browsing a curated database *************************** +**Authors**: Giovanni Pizzi and Francisco Ramirez, August 2020 + 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" `_ to show some examples of useful queries that can help with this. -This will hopefully give you some guidance on how to apply and extend queries for your own needs. +In the following section we will use the database from `"Two-dimensional materials from high-throughput computational exfoliation of experimentally known compounds" `_. +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 was designed using `Quantum Mobile 20.03.0 `_. -You will also need to download and import the `2D materials database `_. -This can be easily done by running the following commands in your aiida environment: +This story can be run in the `Quantum Mobile 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 `_ from `the corresponding Materials Cloud Archive entry `_. +This can be easily done by running the following commands in your AiiDA environment: .. code-block:: bash @@ -21,12 +25,14 @@ This can be easily done by running the following commands in your aiida environm Using the discover section .......................... -In the case of this particular database, an easy to explore interface is available through the `specific Discover section `_ of the Materials Cloud. -This interface will provide a general overview of all materials available, and choosing any of them (let's say, `silver bromide `_) will give you access to of its available calculated properties. -If one was interested in, say, the band structure properties, one could use the explore button next to the `Band gap [eV]: 1.3` to go to the `actual node `_ that contains this information. +In the case of this particular database, an interface that allows easy exploration of curated results is available through the `corresponding Discover section `_ of Materials Cloud. +This interface will provide a general overview of all materials available, and choosing any of them (let's say, `silver bromide `_) 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 `_ 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 this node and see how to check the information in a local database. -We invite the reader to browse the discovery and explore sections for this database on their 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 .............................. @@ -38,9 +44,9 @@ For this we will use the AiiDA interactive shell: (aiida) max@qmobile:~$ verdi shell -Now we will do some manual querying to explore this node properties and connections. +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: +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): .. code-block:: Python @@ -48,14 +54,14 @@ Now we will do some manual querying to explore this node properties and connecti In [2]: bandgap_node Out[2]: -2. One can check the attributes and discover 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 it was necessary). +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). .. code-block:: Python 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 .creator to get the calculation that generated the data node: +3. We start inspecting the provenance using the ``.creator`` method to get the calculation that generated the ``Dict`` data node: .. code-block:: Python @@ -64,7 +70,7 @@ Now we will do some manual querying to explore this node properties and connecti 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 there is tab completion after .inputs.): +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): .. code-block:: Python @@ -72,7 +78,7 @@ Now we will do some manual querying to explore this node properties and connecti 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 (band structure), and see it was a Quantum ESPRESSO run: +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: .. code-block:: Python @@ -88,17 +94,18 @@ Now we will do some manual querying to explore this node properties and connecti Out[10]: -Note that we don't really need all of the intermediate node variables, as all of these steps can just be concatenated in a single chain of propery accessess from our originally identified ``bandgap_node``. -In the end, we wil arrive at the exact same structure node: +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: .. code-block:: Python 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 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’s check its attributes: +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 uss check its attributes: .. code-block:: Python @@ -107,12 +114,13 @@ Let’s check its attributes: 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 to filter by the function name. +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 of this we have a better understanding of the the structure of the data. +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 4 levels up(will be enough for this case). -Not that this has to be executed outside of the verdi shell. +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. .. code-block:: bash @@ -125,10 +133,10 @@ The result should look something like this: Systematic querying of the database ................................... - -Let’s now construct the query using the QueryBuilder. +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 of the lines of code. +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 `_. .. code-block:: Python @@ -140,39 +148,43 @@ There are some comments which explain the purpose of each of the lines of code. # I want, in the end, the 'band_gap' property returned ("projected") # This is in the attributes of the Dict node - # 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') + # 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 has to be generated by a CalculationNode (it's a super class of CalcFunctionNode, - # one could write CalcFunctionNode as well), and I only want those where the + # 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( - CalculationNode, + CalcFunctionNode, filters={'attributes.function_name': 'get_bandgap_inline'}, with_outgoing='bandgap_node', - tag='bandgap_calc', + 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 Quantum ESPRESSO - # in this DB so I don't add more specific filters) + # 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 AiiDA node (indicated with '*') + # the whole AiiDA node (indicated with '*') rather than just some attributes query.append(StructureData, with_outgoing='qe', project='*') - # I have decided to project on two things: the band_gap and the structure node + # 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 8 lines of code (removing the comments 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 treshold. -You can execute the script by running ``verdi run ``. +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. .. code-block:: bash @@ -212,34 +224,35 @@ Here is the output you should obtain if you only have the 2D materials database Band gap for Br2Cr2O2: 0.486 eV Band gap for I2Ni: 0.319 eV -One can also have fun adding more statements before calling `.all()`. +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: -- One can check that the input code of AiiDA was a specific one: +- You can check that the input code of the `qe` calculation was indeed a using the Quantum ESPRESSO plugin (``quantumespresso.pw``): -.. code-block:: python + .. code-block:: python query.append(Code, with_outgoing='qe', filters={'attributes.input_plugin': 'quantumespresso.pw'}) -- One can project back the total running time (wall time) of the Quantum ESPRESSO calculation (it's in an output node with link label 'output_parameters'). For this one needs to add a third element to the tuple when looping over .all(): +- 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()``: -.. code-block:: python + .. code-block:: 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 = {})".format(structure.get_formula(), band_gap,walltime)) + 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 PSQL DB. -We’ve 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: +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: .. code-block:: sql @@ -248,7 +261,9 @@ Just for reference, if you do `print(query)` you get the corresponding SQL state 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, I’d rather stick with the simpler QueryBuilder interface! +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 From 8a785779a50e175320437881d5bbcbc3ee0c86aa Mon Sep 17 00:00:00 2001 From: ramirezfranciscof Date: Mon, 31 Aug 2020 16:36:09 +0200 Subject: [PATCH 05/20] Minor corrections + renaming of repo --- README.md | 2 +- source/stories/browse_discover.rst | 4 ++-- source/stories/index.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6806beb..8de5f99 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# aiida-singledocs +# aiida-blog Single page or other short miscelaneous documentation diff --git a/source/stories/browse_discover.rst b/source/stories/browse_discover.rst index 46402fb..26a89c1 100644 --- a/source/stories/browse_discover.rst +++ b/source/stories/browse_discover.rst @@ -74,7 +74,7 @@ We start by performing some browsing using the AiiDA API to explore the properti .. code-block:: Python - In [6]: bands_node = calculation_node.inputs.bands_node + In [6]: bands_node = calculation_node.inputs.bands In [7]: bands_node Out[7]: @@ -105,7 +105,7 @@ We did it mostly for convenience, but all of these steps can just be concatenate 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 uss check its attributes: +Let us check its attributes: .. code-block:: Python diff --git a/source/stories/index.rst b/source/stories/index.rst index 7f6d47f..9b63a55 100644 --- a/source/stories/index.rst +++ b/source/stories/index.rst @@ -6,7 +6,7 @@ AiiDA user stories This section 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 begining which version of the `Quantum Mobile `_ was used in its design/tests. +Each story will specify at the beginning which version of the `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 `_) ensures the reproducibility of the commands if you want to test them out yourself. Any other further setups required (such as importing databases) will be described in the introduction of the story. From 9f7746dd08bc11a0ab713ea06bad4dadfb168299 Mon Sep 17 00:00:00 2001 From: Giovanni Pizzi Date: Tue, 15 Sep 2020 09:47:30 +0200 Subject: [PATCH 06/20] Fix wrong newline --- source/stories/browse_discover.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/stories/browse_discover.rst b/source/stories/browse_discover.rst index 26a89c1..d55cee3 100644 --- a/source/stories/browse_discover.rst +++ b/source/stories/browse_discover.rst @@ -140,8 +140,7 @@ If you are not familiar with the ``QueryBuilder``, we refer to the `official Aii .. code-block:: Python - from aiida.orm - import QueryBuilder, Dict, CalculationNode, BandsData, StructureData + from aiida.orm import QueryBuilder, Dict, CalculationNode, BandsData, StructureData # Create a new query builder object query = QueryBuilder() From f151b28c2e88b53c4405b63d043edafa5696162e Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 09:20:16 +0200 Subject: [PATCH 07/20] Add initial documentatio --- .gitignore | 2 + .readthedocs.yml | 10 + README.md | 1 + docs/archives/visualising_graphs.aiida | Bin 0 -> 8262 bytes docs/conf.py | 74 + docs/images/aiida-crystal17.jpg | Bin 0 -> 8351 bytes {source => docs}/images/provenance-1.png | Bin docs/index.md | 16 + docs/stories/aiida-crystal17.ipynb | 2272 ++++++++++++++++++++++ docs/stories/browse_discover.md | 264 +++ docs/stories/index.md | 4 + docs/stories/visualising_graphs.ipynb | 307 +++ requirements-docs.txt | 5 + source/conf.py | 52 - source/index.rst | 21 - source/stories/browse_discover.rst | 271 --- source/stories/index.rst | 19 - tox.ini | 24 + 18 files changed, 2979 insertions(+), 363 deletions(-) create mode 100644 .readthedocs.yml create mode 100644 docs/archives/visualising_graphs.aiida create mode 100644 docs/conf.py create mode 100644 docs/images/aiida-crystal17.jpg rename {source => docs}/images/provenance-1.png (100%) create mode 100644 docs/index.md create mode 100644 docs/stories/aiida-crystal17.ipynb create mode 100644 docs/stories/browse_discover.md create mode 100644 docs/stories/index.md create mode 100644 docs/stories/visualising_graphs.ipynb create mode 100644 requirements-docs.txt delete mode 100644 source/conf.py delete mode 100644 source/index.rst delete mode 100644 source/stories/browse_discover.rst delete mode 100644 source/stories/index.rst create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index af96791..6b795ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +.tox .vscode +_build build diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..f467a7b --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,10 @@ +version: 2 + +python: + version: 3 + install: + - requirements: requirements-docs.txt + +sphinx: + builder: html + fail_on_warning: false diff --git a/README.md b/README.md index 8de5f99..f2fc11b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # aiida-blog + Single page or other short miscelaneous documentation diff --git a/docs/archives/visualising_graphs.aiida b/docs/archives/visualising_graphs.aiida new file mode 100644 index 0000000000000000000000000000000000000000..56164574e3d03e53b01f21d3555a8c1ab3a89ed8 GIT binary patch literal 8262 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 E1AwoMTmS$7 literal 0 HcmV?d00001 diff --git a/source/images/provenance-1.png b/docs/images/provenance-1.png similarity index 100% rename from source/images/provenance-1.png rename to docs/images/provenance-1.png 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.ipynb b/docs/stories/visualising_graphs.ipynb new file mode 100644 index 0000000..1a01426 --- /dev/null +++ b/docs/stories/visualising_graphs.ipynb @@ -0,0 +1,307 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to visualize provenance\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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.\n", + "\n", + ":::{note}\n", + "This tutorial can be downloaded and run as a Jupyter Notebook: {download}`visualising_graphs.ipynb`\n", + ":::\n", + "\n", + ":::{seealso}\n", + "`verdi graph generate -h`\n", + ":::\n", + "\n", + "We first load the database and required modules:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "init_cell": true + }, + "outputs": [], + "source": [ + "from aiida import load_profile\n", + "profile = load_profile()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "init_cell": true + }, + "outputs": [], + "source": [ + "from aiida.common import LinkType\n", + "from aiida.orm.utils.links import LinkPair\n", + "from aiida.tools.visualization import Graph, pstate_node_styles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The example provenance graph, used in this tutorial, can be downloaded {download}`from this link <../archives/visualising_graphs.aiida>`\n", + "\n", + "It can then be imported into the database:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "!verdi import -n graph1.aiida" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dict1_uuid = '0ea79a16-501f-408a-8c84-a2704a778e4b'\n", + "calc1_uuid = 'b23e692e-4e01-48dd-b515-4c63877d73a4'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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.\n", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "graph = Graph()\n", + "graph.add_node(dict1_uuid)\n", + "graph.add_node(calc1_uuid)\n", + "graph.graphviz" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "graph.add_edge(\n", + " dict1_uuid, calc1_uuid, \n", + " link_pair=LinkPair(LinkType.INPUT_CALC, \"input1\"))\n", + "graph.graphviz" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "graph.add_incoming(calc1_uuid)\n", + "graph.add_outgoing(calc1_uuid)\n", + "graph.graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The {py:class}`~aiida.tools.visualization.graph.Graph` can also be initialized with global style attributes,\n", + "as outlined in the [graphviz attributes table](https://www.graphviz.org/doc/info/attrs.html)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "graph = Graph(node_id_type=\"uuid\",\n", + " global_node_style={\"penwidth\": 1},\n", + " global_edge_style={\"color\": \"blue\"},\n", + " graph_attr={\"size\": \"8!,8!\", \"rankdir\": \"LR\"})\n", + "graph.add_incoming(calc1_uuid)\n", + "graph.add_outgoing(calc1_uuid)\n", + "graph.graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def link_style(link_pair, **kwargs):\n", + " return {\"color\": \"blue\"}\n", + "\n", + "graph = Graph(node_style_fn=pstate_node_styles,\n", + " link_style_fn=link_style,\n", + " graph_attr={\"size\": \"8!,8!\", \"rankdir\": \"LR\"})\n", + "graph.add_incoming(calc1_uuid)\n", + "graph.add_outgoing(calc1_uuid)\n", + "graph.graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Edges can be annotated by one or both of their edge label and link type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "graph = Graph(graph_attr={\"size\": \"8!,8!\", \"rankdir\": \"LR\"})\n", + "graph.add_incoming(calc1_uuid,\n", + " annotate_links=\"both\")\n", + "graph.add_outgoing(calc1_uuid,\n", + " annotate_links=\"both\")\n", + "graph.graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "graph = Graph(graph_attr={\"size\": \"8!,8!\", \"rankdir\": \"LR\"})\n", + "graph.recurse_descendants(\n", + " dict1_uuid,\n", + " origin_style=None,\n", + " include_process_inputs=True,\n", + " annotate_links=\"both\"\n", + ")\n", + "graph.graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The link types can also be filtered, to view only the 'data' or 'logical' provenance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "graph = Graph(graph_attr={\"size\": \"8,8!\", \"rankdir\": \"LR\"})\n", + "graph.recurse_descendants(\n", + " dict1_uuid, \n", + " origin_style=None,\n", + " include_process_inputs=True,\n", + " annotate_links=\"both\",\n", + " link_types=(\"input_calc\", \"create\")\n", + ")\n", + "graph.graphviz" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "graph = Graph(graph_attr={\"size\": \"8,8!\", \"rankdir\": \"LR\"})\n", + "graph.recurse_descendants(\n", + " dict1_uuid,\n", + " origin_style=None,\n", + " include_process_inputs=True,\n", + " annotate_links=\"both\",\n", + " link_types=(\"input_work\", \"return\")\n", + ")\n", + "graph.graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you wish to highlight specific node classes,\n", + "then the `highlight_classes` option can be used\n", + "to only color specified nodes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "graph = Graph(graph_attr={\"size\": \"20,20\", \"rankdir\": \"LR\"})\n", + "graph.recurse_descendants(\n", + " dict1_uuid, \n", + " highlight_classes=['Dict']\n", + ")\n", + "graph.graphviz" + ] + } + ], + "metadata": { + "blogpost": true, + "author": "Chris Sewell", + "date": "2019-12-01", + "tags": "QM-19.0.1,aiida-1.1", + "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.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..70a0762 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,5 @@ +myst-nb~=0.10.1 +sphinx-book-theme==0.0.39b1 +sphinx-panels~=0.5.2 +ablog +sphinx-autobuild diff --git a/source/conf.py b/source/conf.py deleted file mode 100644 index 0fd5aa2..0000000 --- a/source/conf.py +++ /dev/null @@ -1,52 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- Project information ----------------------------------------------------- - -project = 'aiida-singledocs' -copyright = '2020, Francisco Ramirez' -author = 'Francisco Ramirez' - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# 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 = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file diff --git a/source/index.rst b/source/index.rst deleted file mode 100644 index b32d957..0000000 --- a/source/index.rst +++ /dev/null @@ -1,21 +0,0 @@ -.. aiida-singledocs documentation master file, created by - sphinx-quickstart on Wed Aug 5 16:36:35 2020. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to aiida-singledocs's documentation! -============================================ - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - Stories <./stories/index> - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/source/stories/browse_discover.rst b/source/stories/browse_discover.rst deleted file mode 100644 index d55cee3..0000000 --- a/source/stories/browse_discover.rst +++ /dev/null @@ -1,271 +0,0 @@ -.. _stories:browse_export: - -*************************** -Browsing a curated database -*************************** - -**Authors**: Giovanni Pizzi and Francisco Ramirez, August 2020 - -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" `_. -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 `_ 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 `_ from `the corresponding Materials Cloud Archive entry `_. -This can be easily done by running the following commands in your AiiDA environment: - -.. code-block:: 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 `_ of Materials Cloud. -This interface will provide a general overview of all materials available, and choosing any of them (let's say, `silver bromide `_) 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 `_ 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: - -.. code-block:: 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): - -.. code-block:: Python - - 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). - -.. code-block:: Python - - 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: - -.. code-block:: Python - - 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): - -.. code-block:: Python - - 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: - -.. code-block:: Python - - 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: - -.. code-block:: Python - - 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: - -.. code-block:: Python - - 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: - -.. code-block:: Python - - 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. - -.. code-block:: 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: - -.. figure:: ../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 `_. - -.. code-block:: 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. - -.. code-block:: 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``): - - .. code-block:: 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()``: - - .. code-block:: 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: - -.. code-block:: 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/source/stories/index.rst b/source/stories/index.rst deleted file mode 100644 index 9b63a55..0000000 --- a/source/stories/index.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. _stories: - -AiiDA user stories ------------------- - -This section 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 `_ 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 `_) ensures the reproducibility of the commands if you want to test them out yourself. -Any other further setups required (such as importing databases) will be described in the introduction of the story. - -Here is the list of currently available user stories: - -.. toctree:: - :maxdepth: 1 - :numbered: - - ./browse_discover 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} From bb94f08456971a8b86c76ae8c60cfae3df5ea766 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 09:21:44 +0200 Subject: [PATCH 08/20] Update aiida-crystal17.ipynb --- docs/stories/aiida-crystal17.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stories/aiida-crystal17.ipynb b/docs/stories/aiida-crystal17.ipynb index 5f5cbe8..678bd65 100644 --- a/docs/stories/aiida-crystal17.ipynb +++ b/docs/stories/aiida-crystal17.ipynb @@ -16,7 +16,7 @@ "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" + "![icon](../images/aiida-crystal17.jpg) \n" ] }, { From 9ba2572464e300f6af418a31272e1a4963dde393 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 11:42:42 +0200 Subject: [PATCH 09/20] update --- .readthedocs.yml | 2 + docs/conf.py | 24 +- docs/stories/aiida-crystal17.ipynb | 2 +- docs/stories/visualising_graphs.ipynb | 307 -------------------------- docs/stories/visualising_graphs.md | 175 +++++++++++++++ environment.yml | 3 + requirements-docs.txt | 2 + 7 files changed, 205 insertions(+), 310 deletions(-) delete mode 100644 docs/stories/visualising_graphs.ipynb create mode 100644 docs/stories/visualising_graphs.md create mode 100644 environment.yml diff --git a/.readthedocs.yml b/.readthedocs.yml index f467a7b..04fddc4 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,5 +1,7 @@ version: 2 +conda: + environment: environment.yml python: version: 3 install: diff --git a/docs/conf.py b/docs/conf.py index 5df9646..a5ff877 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,13 +18,13 @@ myst_admonition_enable = True myst_html_img_enable = True -jupyter_execute_notebooks = "off" +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_excerpt = 3 post_auto_image = 1 fontawesome_included = True html_sidebars = { @@ -72,3 +72,23 @@ "use_edit_page_button": True, } panels_add_bootstrap_css = False + + +def start_aiida(*args): + from reentry import manager + from aiida.manage.tests import _GLOBAL_TEST_MANAGER, BACKEND_DJANGO + from aiida.common.utils import Capturing + manager.scan() + with Capturing(): # capture output of AiiDA DB setup + _GLOBAL_TEST_MANAGER.use_temporary_profile(backend=BACKEND_DJANGO, pgtest=None) + from aiida.manage.configuration import settings + import os + 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/stories/aiida-crystal17.ipynb b/docs/stories/aiida-crystal17.ipynb index 678bd65..5f5cbe8 100644 --- a/docs/stories/aiida-crystal17.ipynb +++ b/docs/stories/aiida-crystal17.ipynb @@ -16,7 +16,7 @@ "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" + "![icon](../images/aiida-crystal17.jpg)\n" ] }, { diff --git a/docs/stories/visualising_graphs.ipynb b/docs/stories/visualising_graphs.ipynb deleted file mode 100644 index 1a01426..0000000 --- a/docs/stories/visualising_graphs.ipynb +++ /dev/null @@ -1,307 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# How to visualize provenance\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "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.\n", - "\n", - ":::{note}\n", - "This tutorial can be downloaded and run as a Jupyter Notebook: {download}`visualising_graphs.ipynb`\n", - ":::\n", - "\n", - ":::{seealso}\n", - "`verdi graph generate -h`\n", - ":::\n", - "\n", - "We first load the database and required modules:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "init_cell": true - }, - "outputs": [], - "source": [ - "from aiida import load_profile\n", - "profile = load_profile()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "init_cell": true - }, - "outputs": [], - "source": [ - "from aiida.common import LinkType\n", - "from aiida.orm.utils.links import LinkPair\n", - "from aiida.tools.visualization import Graph, pstate_node_styles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The example provenance graph, used in this tutorial, can be downloaded {download}`from this link <../archives/visualising_graphs.aiida>`\n", - "\n", - "It can then be imported into the database:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "!verdi import -n graph1.aiida" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dict1_uuid = '0ea79a16-501f-408a-8c84-a2704a778e4b'\n", - "calc1_uuid = 'b23e692e-4e01-48dd-b515-4c63877d73a4'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "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.\n", - "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." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "graph = Graph()\n", - "graph.add_node(dict1_uuid)\n", - "graph.add_node(calc1_uuid)\n", - "graph.graphviz" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "graph.add_edge(\n", - " dict1_uuid, calc1_uuid, \n", - " link_pair=LinkPair(LinkType.INPUT_CALC, \"input1\"))\n", - "graph.graphviz" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "graph.add_incoming(calc1_uuid)\n", - "graph.add_outgoing(calc1_uuid)\n", - "graph.graphviz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The {py:class}`~aiida.tools.visualization.graph.Graph` can also be initialized with global style attributes,\n", - "as outlined in the [graphviz attributes table](https://www.graphviz.org/doc/info/attrs.html)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "graph = Graph(node_id_type=\"uuid\",\n", - " global_node_style={\"penwidth\": 1},\n", - " global_edge_style={\"color\": \"blue\"},\n", - " graph_attr={\"size\": \"8!,8!\", \"rankdir\": \"LR\"})\n", - "graph.add_incoming(calc1_uuid)\n", - "graph.add_outgoing(calc1_uuid)\n", - "graph.graphviz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "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." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def link_style(link_pair, **kwargs):\n", - " return {\"color\": \"blue\"}\n", - "\n", - "graph = Graph(node_style_fn=pstate_node_styles,\n", - " link_style_fn=link_style,\n", - " graph_attr={\"size\": \"8!,8!\", \"rankdir\": \"LR\"})\n", - "graph.add_incoming(calc1_uuid)\n", - "graph.add_outgoing(calc1_uuid)\n", - "graph.graphviz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Edges can be annotated by one or both of their edge label and link type." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "graph = Graph(graph_attr={\"size\": \"8!,8!\", \"rankdir\": \"LR\"})\n", - "graph.add_incoming(calc1_uuid,\n", - " annotate_links=\"both\")\n", - "graph.add_outgoing(calc1_uuid,\n", - " annotate_links=\"both\")\n", - "graph.graphviz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "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. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "graph = Graph(graph_attr={\"size\": \"8!,8!\", \"rankdir\": \"LR\"})\n", - "graph.recurse_descendants(\n", - " dict1_uuid,\n", - " origin_style=None,\n", - " include_process_inputs=True,\n", - " annotate_links=\"both\"\n", - ")\n", - "graph.graphviz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The link types can also be filtered, to view only the 'data' or 'logical' provenance." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "graph = Graph(graph_attr={\"size\": \"8,8!\", \"rankdir\": \"LR\"})\n", - "graph.recurse_descendants(\n", - " dict1_uuid, \n", - " origin_style=None,\n", - " include_process_inputs=True,\n", - " annotate_links=\"both\",\n", - " link_types=(\"input_calc\", \"create\")\n", - ")\n", - "graph.graphviz" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "graph = Graph(graph_attr={\"size\": \"8,8!\", \"rankdir\": \"LR\"})\n", - "graph.recurse_descendants(\n", - " dict1_uuid,\n", - " origin_style=None,\n", - " include_process_inputs=True,\n", - " annotate_links=\"both\",\n", - " link_types=(\"input_work\", \"return\")\n", - ")\n", - "graph.graphviz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you wish to highlight specific node classes,\n", - "then the `highlight_classes` option can be used\n", - "to only color specified nodes:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "graph = Graph(graph_attr={\"size\": \"20,20\", \"rankdir\": \"LR\"})\n", - "graph.recurse_descendants(\n", - " dict1_uuid, \n", - " highlight_classes=['Dict']\n", - ")\n", - "graph.graphviz" - ] - } - ], - "metadata": { - "blogpost": true, - "author": "Chris Sewell", - "date": "2019-12-01", - "tags": "QM-19.0.1,aiida-1.1", - "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.7.1" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} 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..0042073 --- /dev/null +++ b/environment.yml @@ -0,0 +1,3 @@ +name: readthedocs +dependencies: + - postgresql diff --git a/requirements-docs.txt b/requirements-docs.txt index 70a0762..8e7a3a3 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -3,3 +3,5 @@ sphinx-book-theme==0.0.39b1 sphinx-panels~=0.5.2 ablog sphinx-autobuild +aiida-core==1.4.2 +pgtest From 1c28b419f35125ce535710135f6e0ee5946364e8 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 11:48:59 +0200 Subject: [PATCH 10/20] update rtd environment --- .readthedocs.yml | 4 ---- environment.yml | 10 ++++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 04fddc4..5716bfd 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,10 +2,6 @@ version: 2 conda: environment: environment.yml -python: - version: 3 - install: - - requirements: requirements-docs.txt sphinx: builder: html diff --git a/environment.yml b/environment.yml index 0042073..8f1c09c 100644 --- a/environment.yml +++ b/environment.yml @@ -1,3 +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 From d5856a94f5a9ec961cedfc01d0049bb9544a8b29 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 11:55:14 +0200 Subject: [PATCH 11/20] Update conf.py --- docs/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index a5ff877..befde7f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -75,14 +75,14 @@ def start_aiida(*args): - from reentry import manager + import os + import subprocess + subprocess.check_call(["reentry", "scan"]) from aiida.manage.tests import _GLOBAL_TEST_MANAGER, BACKEND_DJANGO from aiida.common.utils import Capturing - manager.scan() with Capturing(): # capture output of AiiDA DB setup _GLOBAL_TEST_MANAGER.use_temporary_profile(backend=BACKEND_DJANGO, pgtest=None) from aiida.manage.configuration import settings - import os os.environ["AIIDA_PATH"] = settings.AIIDA_CONFIG_FOLDER def end_aiida(*args): From 827def8913e2cd3d0ea19a24b6cfe3dc5ec8e0d7 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 12:06:36 +0200 Subject: [PATCH 12/20] Update conf.py --- docs/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index befde7f..1708466 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -78,6 +78,8 @@ def start_aiida(*args): import os import subprocess subprocess.check_call(["reentry", "scan"]) + os.environ["LANG"] = "en_US.UTF-8" + os.environ["LC_ALL"] = "en_US.UTF-8" from aiida.manage.tests import _GLOBAL_TEST_MANAGER, BACKEND_DJANGO from aiida.common.utils import Capturing with Capturing(): # capture output of AiiDA DB setup From 7f709c89c29f37c72d0abb4fc238b3528616ed1f Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 12:11:00 +0200 Subject: [PATCH 13/20] Update conf.py --- docs/conf.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 1708466..0406dd8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -78,8 +78,9 @@ def start_aiida(*args): import os import subprocess subprocess.check_call(["reentry", "scan"]) - os.environ["LANG"] = "en_US.UTF-8" - os.environ["LC_ALL"] = "en_US.UTF-8" + print(subprocess.check_output(["locale"])) + # os.environ["LANG"] = "en_US.UTF-8" + # os.environ["LC_ALL"] = "en_US.UTF-8" from aiida.manage.tests import _GLOBAL_TEST_MANAGER, BACKEND_DJANGO from aiida.common.utils import Capturing with Capturing(): # capture output of AiiDA DB setup From cdb2d4d68f1fde3ad3acb7ff04f2df5bd4c91b7e Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 12:12:27 +0200 Subject: [PATCH 14/20] Update conf.py --- docs/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/conf.py b/docs/conf.py index 0406dd8..94c52bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -79,6 +79,7 @@ def start_aiida(*args): import subprocess subprocess.check_call(["reentry", "scan"]) print(subprocess.check_output(["locale"])) + print(subprocess.check_output(["locale", "-a"])) # os.environ["LANG"] = "en_US.UTF-8" # os.environ["LC_ALL"] = "en_US.UTF-8" from aiida.manage.tests import _GLOBAL_TEST_MANAGER, BACKEND_DJANGO From 22e26a1797d0265423c4080eabe4305ba918362e Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 12:17:13 +0200 Subject: [PATCH 15/20] Update conf.py --- docs/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 94c52bd..e2164fe 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -78,6 +78,10 @@ def start_aiida(*args): import os import subprocess subprocess.check_call(["reentry", "scan"]) + try: + print(subprocess.check_output(["locale-gen", "en_US.UTF-8"]) + except: + pass print(subprocess.check_output(["locale"])) print(subprocess.check_output(["locale", "-a"])) # os.environ["LANG"] = "en_US.UTF-8" From 899652772261e58ba950717f52d5b5d46a477eb6 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 12:19:20 +0200 Subject: [PATCH 16/20] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index e2164fe..f30177d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -79,7 +79,7 @@ def start_aiida(*args): import subprocess subprocess.check_call(["reentry", "scan"]) try: - print(subprocess.check_output(["locale-gen", "en_US.UTF-8"]) + print(subprocess.check_output(["locale-gen", "en_US.UTF-8"])) except: pass print(subprocess.check_output(["locale"])) From 15873afcc07cc8f101b7c7e1f179da62301f0fc1 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 12:25:50 +0200 Subject: [PATCH 17/20] Update conf.py --- docs/conf.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index f30177d..75e555d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,7 +5,9 @@ project = 'aiida-singledocs' copyright = '2020, Francisco Ramirez' -author = 'Francisco Ramirez, Chris Sewell' +project = "aiida-singledocs" +copyright = "2020, Francisco Ramirez" +author = "Francisco Ramirez, Chris Sewell" version = "0.0.1" @@ -28,7 +30,7 @@ post_auto_image = 1 fontawesome_included = True html_sidebars = { - "stories/index": ['tagcloud.html', 'archives.html', 'sbt-sidebar-nav.html'], + "stories/index": ["tagcloud.html", "archives.html", "sbt-sidebar-nav.html"], "stories/*": [ "sidebar-search-bs.html", "postcard.html", @@ -38,7 +40,7 @@ "archives.html", "sbt-sidebar-nav.html", "sbt-sidebar-footer.html", - ] + ], } intersphinx_mapping = { @@ -77,10 +79,21 @@ def start_aiida(*args): import os import subprocess + subprocess.check_call(["reentry", "scan"]) try: - print(subprocess.check_output(["locale-gen", "en_US.UTF-8"])) + subprocess.check_output( + [ + "sed", + "-i", + "-e", + "s/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/", + "/etc/locale.gen", + ] + ) + print(subprocess.check_output(["locale-gen"])) except: + print("failed") pass print(subprocess.check_output(["locale"])) print(subprocess.check_output(["locale", "-a"])) @@ -88,15 +101,20 @@ def start_aiida(*args): # os.environ["LC_ALL"] = "en_US.UTF-8" from aiida.manage.tests import _GLOBAL_TEST_MANAGER, BACKEND_DJANGO from aiida.common.utils import Capturing + with Capturing(): # capture output of AiiDA DB setup _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) + app.connect("builder-inited", start_aiida) + app.connect("build-finished", end_aiida) From e1c48c8dee89413f7d3dede67fd513fe3f063959 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 12:30:47 +0200 Subject: [PATCH 18/20] Update conf.py --- docs/conf.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 75e555d..aaadd7e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,8 +3,6 @@ # -- Project information ----------------------------------------------------- -project = 'aiida-singledocs' -copyright = '2020, Francisco Ramirez' project = "aiida-singledocs" copyright = "2020, Francisco Ramirez" author = "Francisco Ramirez, Chris Sewell" @@ -82,6 +80,7 @@ def start_aiida(*args): subprocess.check_call(["reentry", "scan"]) try: + print(subprocess.check_output(["apt-get", "-y", "install", "locales"])) subprocess.check_output( [ "sed", @@ -92,8 +91,8 @@ def start_aiida(*args): ] ) print(subprocess.check_output(["locale-gen"])) - except: - print("failed") + except Exception as err: + print(f"failed: {err}") pass print(subprocess.check_output(["locale"])) print(subprocess.check_output(["locale", "-a"])) From 77515d291d5bcd8b6a83af2b9eb66fe50317e08c Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 12:59:01 +0200 Subject: [PATCH 19/20] Update conf.py --- docs/conf.py | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index aaadd7e..c27a064 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,7 @@ blog_title = "Stories" blog_post_pattern = ["stories/*.md", "stories/*.ipynb"] post_redirect_refresh = 1 -post_auto_excerpt = 3 +post_auto_excerpt = 2 post_auto_image = 1 fontawesome_included = True html_sidebars = { @@ -75,34 +75,28 @@ def start_aiida(*args): + from unittest import mock import os import subprocess subprocess.check_call(["reentry", "scan"]) - try: - print(subprocess.check_output(["apt-get", "-y", "install", "locales"])) - subprocess.check_output( - [ - "sed", - "-i", - "-e", - "s/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/", - "/etc/locale.gen", - ] - ) - print(subprocess.check_output(["locale-gen"])) - except Exception as err: - print(f"failed: {err}") - pass - print(subprocess.check_output(["locale"])) - print(subprocess.check_output(["locale", "-a"])) - # os.environ["LANG"] = "en_US.UTF-8" - # os.environ["LC_ALL"] = "en_US.UTF-8" + from aiida.manage.external import postgres from aiida.manage.tests import _GLOBAL_TEST_MANAGER, BACKEND_DJANGO from aiida.common.utils import Capturing + print(os.environ) + 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 - _GLOBAL_TEST_MANAGER.use_temporary_profile(backend=BACKEND_DJANGO, pgtest=None) + 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 From d7ba97f480135b644a3aa578ec17c2e97132d3ed Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 14 Oct 2020 13:15:30 +0200 Subject: [PATCH 20/20] Update conf.py --- docs/conf.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c27a064..302b936 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,11 +65,14 @@ html_theme_options = { "home_page_in_toc": True, "repository_url": "https://github.com/aiidateam/aiida-blog", - "repository_branch": "develop", + "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 @@ -83,7 +86,7 @@ def start_aiida(*args): from aiida.manage.external import postgres from aiida.manage.tests import _GLOBAL_TEST_MANAGER, BACKEND_DJANGO from aiida.common.utils import Capturing - print(os.environ) + # this is required on READTHEDOCS, since the docker container only contains C.UTF-8 patch = mock.patch.object( postgres, "_CREATE_DB_COMMAND",