From 28bff949d8310b3cca62393e8ee58ca508f645e7 Mon Sep 17 00:00:00 2001 From: JaGeo Date: Tue, 2 Jul 2024 22:28:35 +0200 Subject: [PATCH 1/5] Update Tutorial text --- jobflow.ipynb | 192 +++++++++++++++++++++++++++++--------------------- 1 file changed, 111 insertions(+), 81 deletions(-) diff --git a/jobflow.ipynb b/jobflow.ipynb index 47ab152..65934c8 100644 --- a/jobflow.ipynb +++ b/jobflow.ipynb @@ -31,17 +31,15 @@ { "metadata": {}, "cell_type": "markdown", - "source": [ - "The Python package [`jobflow`](https://materialsproject.github.io/jobflow/index.html) was developed in the context of the [Materials Project](https://next-gen.materialsproject.org/) and its overall flexibility allows for building workflows that go beyond the usage in material science. `jobflow` forms the basis of [`atomate2`](https://materialsproject.github.io/atomate2/index.html), a software package for data generation workflows. `jobflow` and `atomate2` are key packages of the Materials Project. `jobflow` was especially designed to simplify the execution of dynamic workflows -- when the actual number of jobs is dynamically determined upon runtime instead of being statically fixed before running the workflow(s)." - ] + "source": "[`jobflow`](https://materialsproject.github.io/jobflow/index.html) and [`atomate2`](https://materialsproject.github.io/atomate2/index.html) are key packages of the [Materials Project](https://materialsproject.org/) . `jobflow` was especially designed to simplify the execution of dynamic workflows -- when the actual number of jobs is dynamically determined upon runtime instead of being statically fixed before running the workflow(s). `jobflow`'s overall flexibility allows for building workflows that go beyond the usage in material science. `jobflow` forms the basis of `atomate2`. `atomate2` implements data generation workflows in the context of materials science and will be responsible for data generation in the Materials Project in the future. " }, { "metadata": {}, "cell_type": "markdown", "source": [ "## Installation / Setup\n", - "`jobflow` can be installed via 'pip install' and be run with the default setup. It will then internally rely on a predefined memory database as defined in the software package [`maggma`](https://materialsproject.github.io/maggma/). For large-scale usage, a [MongoDB](https://www.mongodb.com/)-like database might be specified for each project in a jobflow.yaml file.\n", - "A high-throughput setup (i.e., parallel execution of independent parts in the workflow) of `jobflow` can be achieved using additional packages like [`fireworks`](https://materialsproject.github.io/fireworks/) or [`jobflow-remote`](https://matgenix.github.io/jobflow-remote/). Both packages require a MongoDB database. In case of `FireWorks`, however, the MongoDB database needs to be connected to the compute nodes. `jobflow-remote` allows remote submission options that only require a MongoDB database on the submitting computer but not the compute nodes. It can also deal with multi-factor authentification." + "`jobflow` can be installed via `pip install` and directly run with the default setup. It will then internally rely on a memory database as defined in the software package [`maggma`](https://materialsproject.github.io/maggma/). For large-scale usage, a [MongoDB](https://www.mongodb.com/)-like database might be specified in a jobflow.yaml file.\n", + "A high-throughput setup (i.e., parallel execution of independent parts in the workflow) of `jobflow` can be achieved using additional packages like [`fireworks`](https://materialsproject.github.io/fireworks/) or [`jobflow-remote`](https://matgenix.github.io/jobflow-remote/). Both packages require a MongoDB database. In case of `FireWorks`, however, the MongoDB database needs to be directly connected to the compute nodes. `jobflow-remote` allows remote submission options that only require a MongoDB database on the submitting computer but not the compute nodes. It can also deal with multi-factor authentification." ] }, { @@ -65,11 +63,14 @@ "cell_type": "code", "source": [ "from jobflow import job\n", + "\n", + "\n", "@job\n", "def my_function(my_parameter: int):\n", " return my_parameter\n", "\n", - "job1 = my_function(my_parameter=1)\n" + "\n", + "job1 = my_function(my_parameter=1)" ], "outputs": [], "execution_count": 2 @@ -77,9 +78,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": [ - "When we connect several `jobs` to generate a `Flow` object, we can construct a whole workflow. The list of connected `jobs` has to be passed to initialize a `Flow`. The order of the `jobs` is automatically determined by the job connectivity via the `job.output` upon runtime. We can also connect several `jobs` and `Flows` to create a new `Flow`.\n" - ] + "source": "When we connect several `job`s to generate a `Flow` object, we can construct a whole workflow. The list of connected `jobs` has to be passed to initialize a `Flow`. The order of the `jobs` is automatically determined by the job connectivity via the `job.output` upon runtime. We can also connect several `job`s and `Flows` to create a new `Flow`.\n" }, { "metadata": { @@ -93,13 +92,18 @@ "execution_count": 3, "source": [ "from jobflow import job, Flow\n", + "\n", + "\n", "@job\n", "def my_function(my_parameter: int):\n", " return my_parameter\n", + "\n", + "\n", "@job\n", "def my_second_function(my_parameter: int):\n", " return my_parameter\n", "\n", + "\n", "job1 = my_function(my_parameter=1)\n", "job2 = my_second_function(job1.output)\n", "flow = Flow([job1, job2], job2.output)" @@ -132,15 +136,17 @@ "from IPython.display import Image, display\n", "from jobflow.utils.graph import to_mermaid\n", "\n", + "\n", "def mm(graph):\n", " graphbytes = graph.encode(\"utf8\")\n", " base64_bytes = base64.b64encode(graphbytes)\n", " base64_string = base64_bytes.decode(\"ascii\")\n", " display(Image(url=\"https://mermaid.ink/img/\" + base64_string))\n", "\n", + "\n", "graph_source = to_mermaid(flow, show_flow_boxes=True)\n", "\n", - "mm(graph_source)\n" + "mm(graph_source)" ], "metadata": { "collapsed": false, @@ -153,9 +159,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": [ - "With a so-called `Maker` object, the implementation of `job` and `Flow` objects can be further simplified. It is an extended dataclass object that generates a `job` or `Flow` with the `make` method. Dataclasses are used as they allow defining immutable default values. These `Makers` can then be used to reuse code via the usual inheritance in Python.\n" - ] + "source": "With a so-called `Maker` object, the implementation of `job` and `Flow` objects can be further simplified. It is an extended dataclass object that generates a `job` or `Flow` with the `make` method. Dataclasses are used as they allow defining immutable default values and simplify initialisation of attributes. These `Makers` can then be used to reuse code via the usual inheritance in Python.\n" }, { "metadata": { @@ -170,6 +174,8 @@ "source": [ "from dataclasses import dataclass\n", "from jobflow import Maker\n", + "\n", + "\n", "@dataclass\n", "class MyMaker(Maker):\n", " name: str = \"My Maker\"\n", @@ -178,30 +184,35 @@ " @job\n", " def make(self, my_parameter: int):\n", " return my_parameter * self.scaling\n", - " \n", + "\n", + "\n", "@dataclass\n", "class MyInheritedMaker(MyMaker):\n", " name: str = \"My inherited Maker\"\n", "\n", - "job1=MyMaker().make(my_parameter=1)\n", - "job2=MyInheritedMaker().make(my_parameter=job1.output)" + "\n", + "job1 = MyMaker().make(my_parameter=1)\n", + "job2 = MyInheritedMaker().make(my_parameter=job1.output)" ] }, { "metadata": {}, "cell_type": "markdown", - "source": [ - "When faced with dynamic workflows (where the number of jobs is unclear before execution) a `Response` object instead of a `job` or `Flow` object can be returned. There are several options to insert the new list of jobs into the `Response` object.\n", - "We will now use this knowledge to implement the Quantum Espresso-related tasks for calculating a \"Energy vs. Volume\" curve. It’s important to point out that this is only a basic implementation, and further extensions towards data validation or for nicer user experience can be added. " - ] + "source": "When faced with dynamic workflows (where the number of jobs is unclear before execution) a `Response` object instead of a `job` or `Flow` object can be returned. There are several options to insert the new list of jobs into the `Response` object." }, { "metadata": {}, "cell_type": "markdown", "source": [ - "> ℹ️ Note: all outputs of a job need to be transformable into a JSON-like format. Therefore, we use a pymatgen `Structure` object here instead of an ase `Atoms` object. `Structure` inherits from the Python package monty’s MSONable class allowing an easy serialization into a `dict`. In addition, commands to execute jobs might be set in such a way that users can easily adapt them in a configuration file." + "## Implementation of Quantum Espresso Workflow\n", + "We will use this knowledge to implement the Quantum Espresso-related tasks for calculating an \"Energy vs. Volume\" curve. It’s important to note that this is only a basic implementation, and further extensions towards data validation or for a simplified user experience can be added. For example, one can typically configure run commands for quantum-chemical programs via configuration files in atomate2." ] }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "> ℹ️ Note: all outputs of a job need to be transformable into a JSON-like format. Therefore, we use a pymatgen `MSONAtoms` object here instead of an ase `Atoms` object. `MSONAtoms` inherits from the Python package monty’s MSONable class allowing an easy serialization into a `dict`. " + }, { "cell_type": "markdown", "source": [ @@ -229,9 +240,7 @@ }, { "cell_type": "markdown", - "source": [ - "Then we import tools for data plotting and mathematical operations and manipulation." - ], + "source": "Then, we import tools for data plotting and mathematical operations and manipulation.", "metadata": { "collapsed": false, "ExecuteTime": { @@ -281,9 +290,7 @@ }, { "cell_type": "markdown", - "source": [ - "From ADIS we take the QE XML output parser." - ], + "source": "Additionally, we take the QE XML output parser written for this tutorial.", "metadata": { "collapsed": false } @@ -305,9 +312,7 @@ }, { "cell_type": "markdown", - "source": [ - "As we want to calculate a (E, V) curve with QE, we start by implementing a function to apply a certain strain to the original structure." - ], + "source": "To calculate a (E, V) curve with QE, we implement a function that applies a certain strain to the original structure.", "metadata": { "collapsed": false } @@ -320,8 +325,7 @@ " for strain in strain_lst:\n", " structure_strain = structure.copy()\n", " structure_strain.set_cell(\n", - " structure_strain.cell * strain**(1/3), \n", - " scale_atoms=True\n", + " structure_strain.cell * strain ** (1 / 3), scale_atoms=True\n", " )\n", " structure_lst.append(structure_strain)\n", " return structure_lst" @@ -375,21 +379,21 @@ "cell_type": "code", "source": [ "def write_input(input_dict, working_directory=\".\"):\n", - " filename = os.path.join(working_directory, 'input.pwi')\n", + " filename = os.path.join(working_directory, \"input.pwi\")\n", " os.makedirs(working_directory, exist_ok=True)\n", " write(\n", - " filename=filename, \n", - " images=input_dict[\"structure\"], \n", - " Crystal=True, \n", - " kpts=input_dict[\"kpts\"], \n", + " filename=filename,\n", + " images=input_dict[\"structure\"],\n", + " Crystal=True,\n", + " kpts=input_dict[\"kpts\"],\n", " input_data={\n", - " 'calculation': input_dict[\"calculation\"],\n", - " 'occupations': 'smearing',\n", - " 'degauss': input_dict[\"smearing\"],\n", - " }, \n", + " \"calculation\": input_dict[\"calculation\"],\n", + " \"occupations\": \"smearing\",\n", + " \"degauss\": input_dict[\"smearing\"],\n", + " },\n", " pseudopotentials=input_dict[\"pseudopotentials\"],\n", - " tstress=True, \n", - " tprnfor=True\n", + " tstress=True,\n", + " tprnfor=True,\n", " )" ], "metadata": { @@ -414,11 +418,11 @@ "cell_type": "code", "source": [ "def collect_output(working_directory=\".\"):\n", - " output = parse_pw(os.path.join(working_directory, 'pwscf.xml'))\n", + " output = parse_pw(os.path.join(working_directory, \"pwscf.xml\"))\n", " return {\n", - " \"structure\": output['ase_structure'],\n", + " \"structure\": output[\"ase_structure\"],\n", " \"energy\": output[\"energy\"],\n", - " \"volume\": output['ase_structure'].get_volume(),\n", + " \"volume\": output[\"ase_structure\"].get_volume(),\n", " }" ], "metadata": { @@ -454,6 +458,7 @@ " \"\"\"\n", " Writes an QE input based on an input_dict\n", " \"\"\"\n", + "\n", " def __init__(self, input_dict):\n", " self.input_dict = input_dict\n", "\n", @@ -466,32 +471,36 @@ " \"\"\"\n", " Generates an QE input based on the format given in QEInputSet.\n", " \"\"\"\n", - " pseudopotentials: dict = field(default_factory=lambda: {\"Al\": \"Al.pbe-n-kjpaw_psl.1.0.0.UPF\"})\n", - " kpts: tuple = (3,3,3)\n", + "\n", + " pseudopotentials: dict = field(\n", + " default_factory=lambda: {\"Al\": \"Al.pbe-n-kjpaw_psl.1.0.0.UPF\"}\n", + " )\n", + " kpts: tuple = (3, 3, 3)\n", " calculation: str = \"vc-relax\"\n", " smearing: float = 0.02\n", - " \n", "\n", " def get_input_set(self, structure) -> QEInputSet:\n", "\n", - " input_dict={\"structure\":structure,\n", - " \"pseudopotentials\":self.pseudopotentials, \n", + " input_dict = {\n", + " \"structure\": structure,\n", + " \"pseudopotentials\": self.pseudopotentials,\n", " \"kpts\": self.kpts,\n", " \"calculation\": self.calculation,\n", " \"smearing\": self.smearing,\n", - " }\n", + " }\n", " return QEInputSet(input_dict=input_dict)\n", "\n", + "\n", "@dataclass\n", "class QEInputStaticGenerator(QEInputGenerator):\n", " calculation: str = \"scf\"\n", "\n", - " \n", - "def write_qe_input_set(structure, input_set_generator=QEInputGenerator(), working_directory=\".\"):\n", + "\n", + "def write_qe_input_set(\n", + " structure, input_set_generator=QEInputGenerator(), working_directory=\".\"\n", + "):\n", " qis = input_set_generator.get_input_set(structure=structure)\n", - " qis.write_input(working_directory=working_directory)\n", - " \n", - " " + " qis.write_input(working_directory=working_directory)" ], "metadata": { "collapsed": false, @@ -521,21 +530,28 @@ "from typing import Any, Optional, Union\n", "\n", "\n", - "QE_CMD= \"mpirun -np 1 pw.x -in input.pwi > output.pwo\"\n", + "QE_CMD = \"mpirun -np 1 pw.x -in input.pwi > output.pwo\"\n", + "\n", + "\n", "def run_qe(qe_cmd=QE_CMD):\n", " subprocess.check_output(qe_cmd, shell=True, universal_newlines=True)\n", - " \n", - " \n", + "\n", + "\n", "class QETaskDoc(BaseModel):\n", " structure: Optional[MSONAtoms] = Field(None, description=\"ASE structure\")\n", " energy: Optional[float] = Field(None, description=\"DFT energy in eV\")\n", " volume: Optional[float] = Field(None, description=\"volume in Angstrom^3\")\n", - " \n", + "\n", " @classmethod\n", " def from_directory(cls, working_directory):\n", - " output=collect_output(working_directory=working_directory)\n", + " output = collect_output(working_directory=working_directory)\n", " # structure object needs to be serializable, i.e., we need an additional transformation\n", - " return cls(structure=MSONAtoms(output[\"structure\"]), energy=output[\"energy\"], volume=output[\"volume\"])\n", + " return cls(\n", + " structure=MSONAtoms(output[\"structure\"]),\n", + " energy=output[\"energy\"],\n", + " volume=output[\"volume\"],\n", + " )\n", + "\n", "\n", "@dataclass\n", "class BaseQEMaker(Maker):\n", @@ -554,9 +570,7 @@ " input_set_generator: QEInputGenerator = field(default_factory=QEInputGenerator)\n", "\n", " @job(output_schema=QETaskDoc)\n", - " def make(\n", - " self, structure\n", - " ) -> QETaskDoc:\n", + " def make(self, structure) -> QETaskDoc:\n", " \"\"\"\n", " Run a QE calculation.\n", "\n", @@ -564,7 +578,7 @@ " ----------\n", " structure : MSONAtoms|Atoms\n", " An Atoms or MSONAtoms object.\n", - " \n", + "\n", " Returns\n", " -------\n", " Output of a QE calculation\n", @@ -573,16 +587,18 @@ "\n", " # write qe input files\n", " write_qe_input_set(\n", - " structure=structure, input_set_generator=self.input_set_generator)\n", + " structure=structure, input_set_generator=self.input_set_generator\n", + " )\n", "\n", " # run the QE software\n", " run_qe()\n", "\n", " # parse QE outputs in form of a task document\n", - " task_doc=QETaskDoc.from_directory(\".\")\n", - " \n", + " task_doc = QETaskDoc.from_directory(\".\")\n", + "\n", " return task_doc\n", "\n", + "\n", "@dataclass\n", "class StaticQEMaker(BaseQEMaker):\n", " \"\"\"\n", @@ -597,7 +613,9 @@ " \"\"\"\n", "\n", " name: str = \"static qe job\"\n", - " input_set_generator: QEInputGenerator = field(default_factory=QEInputStaticGenerator)\n" + " input_set_generator: QEInputGenerator = field(\n", + " default_factory=QEInputStaticGenerator\n", + " )" ], "metadata": { "ExecuteTime": { @@ -752,11 +770,12 @@ "from jobflow import job, Response, Flow, run_locally\n", "\n", "# set up QE PP env\n", - "os.environ['ESPRESSO_PSEUDO'] = f\"{os.getcwd()}/espresso/pseudo\"\n", + "os.environ[\"ESPRESSO_PSEUDO\"] = f\"{os.getcwd()}/espresso/pseudo\"\n", + "\n", "\n", "@job\n", "def get_ev_curve(structure, strain_lst):\n", - " structures=generate_structures(structure,strain_lst=strain_lst)\n", + " structures = generate_structures(structure, strain_lst=strain_lst)\n", " jobs = []\n", " volumes = []\n", " energies = []\n", @@ -765,22 +784,33 @@ " jobs.append(new_job)\n", " volumes.append(new_job.output.volume)\n", " energies.append(new_job.output.energy)\n", - " return Response(replace=Flow(jobs, output={\"energies\": energies, \"volumes\": volumes}))\n", - " \n", + " return Response(\n", + " replace=Flow(jobs, output={\"energies\": energies, \"volumes\": volumes})\n", + " )\n", + "\n", + "\n", "@job\n", "def plot_energy_volume_curve_job(volume_lst, energy_lst):\n", " plot_energy_volume_curve(volume_lst=volume_lst, energy_lst=energy_lst)\n", "\n", - "structure = bulk('Al', a=4.15, cubic=True)\n", + "\n", + "structure = bulk(\"Al\", a=4.15, cubic=True)\n", "relax = BaseQEMaker().make(structure=MSONAtoms(structure))\n", - "ev_curve_data = get_ev_curve(relax.output.structure, strain_lst=np.linspace(0.9, 1.1, 5))\n", + "ev_curve_data = get_ev_curve(\n", + " relax.output.structure, strain_lst=np.linspace(0.9, 1.1, 5)\n", + ")\n", "# structure optimization job and (E, V) curve data job connected via relax.output\n", - "plot_curve = plot_energy_volume_curve_job(volume_lst=ev_curve_data.output[\"volumes\"], energy_lst=ev_curve_data.output[\"energies\"])\n", + "plot_curve = plot_energy_volume_curve_job(\n", + " volume_lst=ev_curve_data.output[\"volumes\"],\n", + " energy_lst=ev_curve_data.output[\"energies\"],\n", + ")\n", "# (E, V) curve data job and plotting the curve job connected via ev_curve_data.output\n", - "qe_flow = [relax, ev_curve_data, plot_curve] \n", + "qe_flow = [relax, ev_curve_data, plot_curve]\n", "qe_fw = Flow(qe_flow)\n", "# qe_flow is the QE flow that consists of the job for structural optimization, calculating the (E, V) curve data points and plotting the curve\n", - "run_locally(qe_fw, create_folders=True) # order of the jobs in the flow is determined by connectivity\n", + "run_locally(\n", + " qe_fw, create_folders=True\n", + ") # order of the jobs in the flow is determined by connectivity\n", "\n", "graph = to_mermaid(qe_fw, show_flow_boxes=True)\n", "mm(graph)" @@ -817,7 +847,7 @@ ], "source": [ "mm(\n", - "\"\"\"\n", + " \"\"\"\n", "flowchart TD\n", " 6883bfe0-2b20-49de-92df-c166d6f91dbc(base qe job) --> |output.structure| dfc9a5cd-fb91-4582-b6c6-c42b4c65cb83(get_ev_curve)\n", " dfc9a5cd-fb91-4582-b6c6-c42b4c65cb83(get_ev_curve) --> |'volumes', 'energies'| 92d14a25-bb90-4c86-b970-af05db90550e(plot_energy_volume_curve_job)\n", From d3d4a6c45869fd2181453869afb40c8f80367405 Mon Sep 17 00:00:00 2001 From: JaGeo Date: Wed, 3 Jul 2024 14:01:42 +0200 Subject: [PATCH 2/5] Additional text iteration and code improvements --- jobflow.ipynb | 413 +++++++++++++++++++++++++------------------------- 1 file changed, 204 insertions(+), 209 deletions(-) diff --git a/jobflow.ipynb b/jobflow.ipynb index 65934c8..3e7f9eb 100644 --- a/jobflow.ipynb +++ b/jobflow.ipynb @@ -31,7 +31,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "[`jobflow`](https://materialsproject.github.io/jobflow/index.html) and [`atomate2`](https://materialsproject.github.io/atomate2/index.html) are key packages of the [Materials Project](https://materialsproject.org/) . `jobflow` was especially designed to simplify the execution of dynamic workflows -- when the actual number of jobs is dynamically determined upon runtime instead of being statically fixed before running the workflow(s). `jobflow`'s overall flexibility allows for building workflows that go beyond the usage in material science. `jobflow` forms the basis of `atomate2`. `atomate2` implements data generation workflows in the context of materials science and will be responsible for data generation in the Materials Project in the future. " + "source": "[`jobflow`](https://materialsproject.github.io/jobflow/index.html) and [`atomate2`](https://materialsproject.github.io/atomate2/index.html) are key packages of the [Materials Project](https://materialsproject.org/) . `jobflow` was especially designed to simplify the execution of dynamic workflows -- when the actual number of jobs is dynamically determined upon runtime instead of being statically fixed before running the workflow(s). `jobflow`'s overall flexibility allows for building workflows that go beyond the usage in materials science. `jobflow` forms the basis of `atomate2`. `atomate2` implements data generation workflows in the context of materials science and will be responsible for data generation in the Materials Project in the future. " }, { "metadata": {}, @@ -52,28 +52,26 @@ }, { "metadata": { - "jupyter": { - "is_executing": true - }, "ExecuteTime": { - "end_time": "2024-06-30T21:48:46.425490304Z", - "start_time": "2024-06-30T21:48:45.207125865Z" + "end_time": "2024-07-03T11:59:43.383373Z", + "start_time": "2024-07-03T11:59:42.489756Z" } }, "cell_type": "code", "source": [ + "from __future__ import annotations\n", "from jobflow import job\n", "\n", "\n", "@job\n", - "def my_function(my_parameter: int):\n", + "def my_function(my_parameter: int)->int:\n", " return my_parameter\n", "\n", "\n", "job1 = my_function(my_parameter=1)" ], "outputs": [], - "execution_count": 2 + "execution_count": 1 }, { "metadata": {}, @@ -83,31 +81,31 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-30T21:48:46.430179016Z", - "start_time": "2024-06-30T21:48:46.428480615Z" + "end_time": "2024-07-03T11:59:43.391580Z", + "start_time": "2024-07-03T11:59:43.386059Z" } }, "cell_type": "code", - "outputs": [], - "execution_count": 3, "source": [ "from jobflow import job, Flow\n", "\n", "\n", "@job\n", - "def my_function(my_parameter: int):\n", + "def my_function(my_parameter: int)->int:\n", " return my_parameter\n", "\n", "\n", "@job\n", - "def my_second_function(my_parameter: int):\n", + "def my_second_function(my_parameter: int)->int:\n", " return my_parameter\n", "\n", "\n", "job1 = my_function(my_parameter=1)\n", "job2 = my_second_function(job1.output)\n", "flow = Flow([job1, job2], job2.output)" - ] + ], + "outputs": [], + "execution_count": 2 }, { "cell_type": "markdown", @@ -120,17 +118,6 @@ }, { "cell_type": "code", - "execution_count": 4, - "outputs": [ - { - "data": { - "text/html": "", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ "import base64\n", "from IPython.display import Image, display\n", @@ -151,10 +138,25 @@ "metadata": { "collapsed": false, "ExecuteTime": { - "end_time": "2024-06-30T21:48:48.253403373Z", - "start_time": "2024-06-30T21:48:48.050654446Z" + "end_time": "2024-07-03T11:59:43.810036Z", + "start_time": "2024-07-03T11:59:43.392933Z" } - } + }, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 3 }, { "metadata": {}, @@ -164,13 +166,11 @@ { "metadata": { "ExecuteTime": { - "end_time": "2024-06-30T21:48:50.046004482Z", - "start_time": "2024-06-30T21:48:50.036716636Z" + "end_time": "2024-07-03T11:59:43.817918Z", + "start_time": "2024-07-03T11:59:43.811514Z" } }, "cell_type": "code", - "outputs": [], - "execution_count": 5, "source": [ "from dataclasses import dataclass\n", "from jobflow import Maker\n", @@ -182,7 +182,7 @@ " scaling: float = 1.1\n", "\n", " @job\n", - " def make(self, my_parameter: int):\n", + " def make(self, my_parameter: int)->float:\n", " return my_parameter * self.scaling\n", "\n", "\n", @@ -193,7 +193,9 @@ "\n", "job1 = MyMaker().make(my_parameter=1)\n", "job2 = MyInheritedMaker().make(my_parameter=job1.output)" - ] + ], + "outputs": [], + "execution_count": 4 }, { "metadata": {}, @@ -215,9 +217,7 @@ }, { "cell_type": "markdown", - "source": [ - "For building the Quantum Espresso (QE)-related tasks, we start by importing essential tools to be able to create and navigate directories (`os`) and execute programs like the QE binary (`subprocess`). `pydantic` is very convenient for managing all kinds of data types." - ], + "source": "For building the Quantum Espresso (QE)-related tasks, we start by importing essential tools to be able to create and navigate directories (`os`) and execute programs like the QE binary (`subprocess`). `pydantic` is very convenient for validating all kinds of data types. These Pydantic models can then also be directly used for API development as part of [FAST-API](https://fastapi.tiangolo.com/).", "metadata": { "collapsed": false } @@ -231,12 +231,12 @@ ], "metadata": { "ExecuteTime": { - "end_time": "2024-06-30T21:48:52.311875326Z", - "start_time": "2024-06-30T21:48:52.302010359Z" + "end_time": "2024-07-03T11:59:43.822983Z", + "start_time": "2024-07-03T11:59:43.820356Z" } }, - "execution_count": 6, - "outputs": [] + "outputs": [], + "execution_count": 5 }, { "cell_type": "markdown", @@ -257,12 +257,12 @@ ], "metadata": { "ExecuteTime": { - "end_time": "2024-06-30T21:48:53.766161056Z", - "start_time": "2024-06-30T21:48:53.587585176Z" + "end_time": "2024-07-03T11:59:44.103958Z", + "start_time": "2024-07-03T11:59:43.824324Z" } }, - "execution_count": 7, - "outputs": [] + "outputs": [], + "execution_count": 6 }, { "cell_type": "markdown", @@ -281,12 +281,12 @@ ], "metadata": { "ExecuteTime": { - "end_time": "2024-06-30T21:48:54.880093570Z", - "start_time": "2024-06-30T21:48:54.760953766Z" + "end_time": "2024-07-03T11:59:44.490896Z", + "start_time": "2024-07-03T11:59:44.105647Z" } }, - "execution_count": 8, - "outputs": [] + "outputs": [], + "execution_count": 7 }, { "cell_type": "markdown", @@ -297,18 +297,18 @@ }, { "cell_type": "code", - "execution_count": 9, - "outputs": [], "source": [ "from adis_tools.parsers import parse_pw" ], "metadata": { "collapsed": false, "ExecuteTime": { - "end_time": "2024-06-30T21:48:55.896681674Z", - "start_time": "2024-06-30T21:48:55.826305046Z" + "end_time": "2024-07-03T11:59:44.614351Z", + "start_time": "2024-07-03T11:59:44.492579Z" } - } + }, + "outputs": [], + "execution_count": 8 }, { "cell_type": "markdown", @@ -320,7 +320,10 @@ { "cell_type": "code", "source": [ - "def generate_structures(structure, strain_lst): # structure should be of ase Atoms type\n", + "from pymatgen.io.ase import MSONAtoms\n", + "from ase import Atoms\n", + "def generate_structures(structure: Atoms, strain_lst: list(float)): # structure should be of ase Atoms type\n", + " structure=MSONAtoms(structure)\n", " structure_lst = []\n", " for strain in strain_lst:\n", " structure_strain = structure.copy()\n", @@ -332,12 +335,12 @@ ], "metadata": { "ExecuteTime": { - "end_time": "2024-06-30T21:48:56.861445479Z", - "start_time": "2024-06-30T21:48:56.853288871Z" + "end_time": "2024-07-03T11:59:45.187502Z", + "start_time": "2024-07-03T11:59:44.615864Z" } }, - "execution_count": 10, - "outputs": [] + "outputs": [], + "execution_count": 9 }, { "cell_type": "markdown", @@ -351,7 +354,7 @@ { "cell_type": "code", "source": [ - "def plot_energy_volume_curve(volume_lst, energy_lst):\n", + "def plot_energy_volume_curve(volume_lst: list(float), energy_lst:list(float)):\n", " plt.plot(volume_lst, energy_lst)\n", " plt.xlabel(\"Volume\")\n", " plt.ylabel(\"Energy\")\n", @@ -359,12 +362,12 @@ ], "metadata": { "ExecuteTime": { - "end_time": "2024-06-30T21:48:57.897136033Z", - "start_time": "2024-06-30T21:48:57.887720116Z" + "end_time": "2024-07-03T11:59:45.196059Z", + "start_time": "2024-07-03T11:59:45.190692Z" } }, - "execution_count": 11, - "outputs": [] + "outputs": [], + "execution_count": 10 }, { "cell_type": "markdown", @@ -378,7 +381,7 @@ { "cell_type": "code", "source": [ - "def write_input(input_dict, working_directory=\".\"):\n", + "def write_input(input_dict: dict, working_directory: str=\".\"):\n", " filename = os.path.join(working_directory, \"input.pwi\")\n", " os.makedirs(working_directory, exist_ok=True)\n", " write(\n", @@ -398,12 +401,12 @@ ], "metadata": { "ExecuteTime": { - "end_time": "2024-06-30T21:48:58.798035578Z", - "start_time": "2024-06-30T21:48:58.791457316Z" + "end_time": "2024-07-03T11:59:45.203075Z", + "start_time": "2024-07-03T11:59:45.198354Z" } }, - "execution_count": 12, - "outputs": [] + "outputs": [], + "execution_count": 11 }, { "cell_type": "markdown", @@ -420,19 +423,19 @@ "def collect_output(working_directory=\".\"):\n", " output = parse_pw(os.path.join(working_directory, \"pwscf.xml\"))\n", " return {\n", - " \"structure\": output[\"ase_structure\"],\n", + " \"structure\": MSONAtoms(output[\"ase_structure\"]),\n", " \"energy\": output[\"energy\"],\n", " \"volume\": output[\"ase_structure\"].get_volume(),\n", " }" ], "metadata": { "ExecuteTime": { - "end_time": "2024-06-30T21:48:59.718169308Z", - "start_time": "2024-06-30T21:48:59.711684823Z" + "end_time": "2024-07-03T11:59:45.208945Z", + "start_time": "2024-07-03T11:59:45.205071Z" } }, - "execution_count": 13, - "outputs": [] + "outputs": [], + "execution_count": 12 }, { "cell_type": "markdown", @@ -497,20 +500,20 @@ "\n", "\n", "def write_qe_input_set(\n", - " structure, input_set_generator=QEInputGenerator(), working_directory=\".\"\n", - "):\n", + " structure: Atoms, input_set_generator: InputGenerator=QEInputGenerator(), working_directory: str=\".\"\n", + ")->None:\n", " qis = input_set_generator.get_input_set(structure=structure)\n", " qis.write_input(working_directory=working_directory)" ], "metadata": { "collapsed": false, "ExecuteTime": { - "end_time": "2024-06-30T21:49:00.757248540Z", - "start_time": "2024-06-30T21:49:00.748276484Z" + "end_time": "2024-07-03T11:59:45.220507Z", + "start_time": "2024-07-03T11:59:45.210945Z" } }, - "execution_count": 14, - "outputs": [] + "outputs": [], + "execution_count": 13 }, { "cell_type": "markdown", @@ -526,14 +529,13 @@ "source": [ "from dataclasses import dataclass, field\n", "from jobflow import job, Maker\n", - "from pymatgen.io.ase import MSONAtoms\n", "from typing import Any, Optional, Union\n", "\n", "\n", "QE_CMD = \"mpirun -np 1 pw.x -in input.pwi > output.pwo\"\n", "\n", "\n", - "def run_qe(qe_cmd=QE_CMD):\n", + "def run_qe(qe_cmd: str=QE_CMD):\n", " subprocess.check_output(qe_cmd, shell=True, universal_newlines=True)\n", "\n", "\n", @@ -547,7 +549,7 @@ " output = collect_output(working_directory=working_directory)\n", " # structure object needs to be serializable, i.e., we need an additional transformation\n", " return cls(\n", - " structure=MSONAtoms(output[\"structure\"]),\n", + " structure=output[\"structure\"],\n", " energy=output[\"energy\"],\n", " volume=output[\"volume\"],\n", " )\n", @@ -570,7 +572,7 @@ " input_set_generator: QEInputGenerator = field(default_factory=QEInputGenerator)\n", "\n", " @job(output_schema=QETaskDoc)\n", - " def make(self, structure) -> QETaskDoc:\n", + " def make(self, structure: Atoms|MSONAtoms) -> QETaskDoc:\n", " \"\"\"\n", " Run a QE calculation.\n", "\n", @@ -619,209 +621,207 @@ ], "metadata": { "ExecuteTime": { - "end_time": "2024-06-30T21:49:02.075801908Z", - "start_time": "2024-06-30T21:49:01.757212927Z" + "end_time": "2024-07-03T11:59:45.236298Z", + "start_time": "2024-07-03T11:59:45.224011Z" } }, - "execution_count": 15, - "outputs": [] + "outputs": [], + "execution_count": 14 }, { "cell_type": "markdown", - "source": [ - "Finally, it's time to orchestrate all functions and classes together in an actual flow. Note how the number of jobs in `get_ev_curve` can be flexibly controlled by using `strain_lst` and therefore we use `Response` to handle the job output. By making `get_ev_curve` and `plot_energy_volume_curve_job` into `job` objects using the `@job` decorator, we ensure that first all the (E, V) data points are calculated before they are plotted. The `qe_flow` contains the list of the jobs that need to be executed in this workflow. The jobs are connected by the respective `job.output` objects that also ensures the correct order in executing the jobs." - ], + "source": "Finally, it's time to orchestrate all functions and classes together in an actual flow. Note how the number of jobs in `get_ev_curve` can be flexibly controlled by using `strain_lst` and therefore we use a `Response` object to handle the flexible job output. By making `get_ev_curve` and `plot_energy_volume_curve_job` into `job` objects using the `@job` decorator, we ensure that first all the (E, V) data points are calculated before they are plotted. The `qe_flow` contains the list of the jobs that need to be executed in this workflow. The jobs are connected by the respective `job.output` objects that also ensures the correct order in executing the jobs.", "metadata": { "collapsed": false } }, { "cell_type": "code", - "execution_count": 16, + "source": [ + "from jobflow import job, Response, Flow, run_locally\n", + "\n", + "# set up QE PP env\n", + "os.environ[\"ESPRESSO_PSEUDO\"] = f\"{os.getcwd()}/espresso/pseudo\"\n", + "\n", + "\n", + "@job\n", + "def get_ev_curve(structure: Atoms|MSONAtoms, strain_lst: list(float)):\n", + " structures = generate_structures(structure, strain_lst=strain_lst)\n", + " jobs = []\n", + " volumes = []\n", + " energies = []\n", + " for istructure in range(len(strain_lst)):\n", + " new_job = StaticQEMaker().make(structures[istructure])\n", + " jobs.append(new_job)\n", + " volumes.append(new_job.output.volume)\n", + " energies.append(new_job.output.energy)\n", + " return Response(\n", + " replace=Flow(jobs, output={\"energies\": energies, \"volumes\": volumes})\n", + " )\n", + "\n", + "\n", + "@job\n", + "def plot_energy_volume_curve_job(volume_lst: list(float), energy_lst: list(float)):\n", + " plot_energy_volume_curve(volume_lst=volume_lst, energy_lst=energy_lst)\n", + "\n", + "\n", + "structure = bulk(\"Al\", a=4.15, cubic=True)\n", + "relax = BaseQEMaker().make(structure=MSONAtoms(structure))\n", + "ev_curve_data = get_ev_curve(\n", + " relax.output.structure, strain_lst=np.linspace(0.9, 1.1, 5)\n", + ")\n", + "# structure optimization job and (E, V) curve data job connected via relax.output\n", + "plot_curve = plot_energy_volume_curve_job(\n", + " volume_lst=ev_curve_data.output[\"volumes\"],\n", + " energy_lst=ev_curve_data.output[\"energies\"],\n", + ")\n", + "# (E, V) curve data job and plotting the curve job connected via ev_curve_data.output\n", + "qe_flow = [relax, ev_curve_data, plot_curve]\n", + "qe_fw = Flow(qe_flow)\n", + "# qe_flow is the QE flow that consists of the job for structural optimization, calculating the (E, V) curve data points and plotting the curve\n", + "run_locally(\n", + " qe_fw, create_folders=True\n", + ") # order of the jobs in the flow is determined by connectivity\n", + "\n", + "graph = to_mermaid(qe_fw, show_flow_boxes=True)\n", + "mm(graph)" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-07-03T12:01:06.585706Z", + "start_time": "2024-07-03T11:59:45.237648Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "2024-06-30 23:49:02,706 INFO Started executing jobs locally\n", - "2024-06-30 23:49:02,710 INFO Starting job - base qe job (6883bfe0-2b20-49de-92df-c166d6f91dbc)\n" + "2024-07-03 13:59:45,257 INFO Started executing jobs locally\n", + "2024-07-03 13:59:45,266 INFO Starting job - base qe job (fb7e4f95-15fe-4ae0-aa3b-3d0f5dc8b65a)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Authorization required, but no authorization protocol specified\n", - "Authorization required, but no authorization protocol specified\n" + "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "2024-06-30 23:49:32,255 INFO Finished job - base qe job (6883bfe0-2b20-49de-92df-c166d6f91dbc)\n", - "2024-06-30 23:49:32,256 INFO Starting job - get_ev_curve (dfc9a5cd-fb91-4582-b6c6-c42b4c65cb83)\n", - "2024-06-30 23:49:32,268 INFO Finished job - get_ev_curve (dfc9a5cd-fb91-4582-b6c6-c42b4c65cb83)\n", - "2024-06-30 23:49:32,271 INFO Starting job - static qe job (0a57429a-2360-4293-8f2a-5323a1173570)\n" + "2024-07-03 14:00:31,075 INFO Finished job - base qe job (fb7e4f95-15fe-4ae0-aa3b-3d0f5dc8b65a)\n", + "2024-07-03 14:00:31,078 INFO Starting job - get_ev_curve (8df443c4-f690-4c1b-9126-8455b6458d9d)\n", + "2024-07-03 14:00:31,099 INFO Finished job - get_ev_curve (8df443c4-f690-4c1b-9126-8455b6458d9d)\n", + "2024-07-03 14:00:31,105 INFO Starting job - static qe job (8c2ce097-6b17-43dd-9f03-d4477338688b)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Authorization required, but no authorization protocol specified\n", - "Authorization required, but no authorization protocol specified\n" + "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "2024-06-30 23:49:35,977 INFO Finished job - static qe job (0a57429a-2360-4293-8f2a-5323a1173570)\n", - "2024-06-30 23:49:35,979 INFO Starting job - static qe job (71996726-fb51-4a1f-8585-373a470bd16c)\n" + "2024-07-03 14:00:37,026 INFO Finished job - static qe job (8c2ce097-6b17-43dd-9f03-d4477338688b)\n", + "2024-07-03 14:00:37,029 INFO Starting job - static qe job (046ab976-1dc9-4b45-816d-86297a209912)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Authorization required, but no authorization protocol specified\n", - "Authorization required, but no authorization protocol specified\n" + "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "2024-06-30 23:49:39,977 INFO Finished job - static qe job (71996726-fb51-4a1f-8585-373a470bd16c)\n", - "2024-06-30 23:49:39,982 INFO Starting job - static qe job (2cf7b6c9-1f19-4b2f-b85f-df201d546c26)\n" + "2024-07-03 14:00:43,878 INFO Finished job - static qe job (046ab976-1dc9-4b45-816d-86297a209912)\n", + "2024-07-03 14:00:43,883 INFO Starting job - static qe job (2e1199a8-12c0-4df0-afdc-2543f0a1652b)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Authorization required, but no authorization protocol specified\n", - "Authorization required, but no authorization protocol specified\n" + "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "2024-06-30 23:49:44,274 INFO Finished job - static qe job (2cf7b6c9-1f19-4b2f-b85f-df201d546c26)\n", - "2024-06-30 23:49:44,275 INFO Starting job - static qe job (a04f788d-9bc1-4453-9897-5d0b4e9321d3)\n" + "2024-07-03 14:00:50,753 INFO Finished job - static qe job (2e1199a8-12c0-4df0-afdc-2543f0a1652b)\n", + "2024-07-03 14:00:50,759 INFO Starting job - static qe job (b6118649-654f-445c-ba44-091c6bf6a5af)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Authorization required, but no authorization protocol specified\n", - "Authorization required, but no authorization protocol specified\n" + "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "2024-06-30 23:49:49,249 INFO Finished job - static qe job (a04f788d-9bc1-4453-9897-5d0b4e9321d3)\n", - "2024-06-30 23:49:49,254 INFO Starting job - static qe job (0ed3d090-bd30-49da-96ed-143b8e7f5b4a)\n" + "2024-07-03 14:00:58,591 INFO Finished job - static qe job (b6118649-654f-445c-ba44-091c6bf6a5af)\n", + "2024-07-03 14:00:58,598 INFO Starting job - static qe job (ed35e52e-fd72-495a-ae7f-4f2f949d8031)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Authorization required, but no authorization protocol specified\n", - "Authorization required, but no authorization protocol specified\n" + "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "2024-06-30 23:49:54,070 INFO Finished job - static qe job (0ed3d090-bd30-49da-96ed-143b8e7f5b4a)\n", - "2024-06-30 23:49:54,071 INFO Starting job - store_inputs (dfc9a5cd-fb91-4582-b6c6-c42b4c65cb83, 2)\n", - "2024-06-30 23:49:54,073 INFO Finished job - store_inputs (dfc9a5cd-fb91-4582-b6c6-c42b4c65cb83, 2)\n", - "2024-06-30 23:49:54,074 INFO Starting job - plot_energy_volume_curve_job (92d14a25-bb90-4c86-b970-af05db90550e)\n", - "2024-06-30 23:49:54,163 INFO Finished job - plot_energy_volume_curve_job (92d14a25-bb90-4c86-b970-af05db90550e)\n", - "2024-06-30 23:49:54,173 INFO Finished executing jobs locally\n" + "2024-07-03 14:01:06,332 INFO Finished job - static qe job (ed35e52e-fd72-495a-ae7f-4f2f949d8031)\n", + "2024-07-03 14:01:06,337 INFO Starting job - store_inputs (8df443c4-f690-4c1b-9126-8455b6458d9d, 2)\n", + "2024-07-03 14:01:06,340 INFO Finished job - store_inputs (8df443c4-f690-4c1b-9126-8455b6458d9d, 2)\n", + "2024-07-03 14:01:06,343 INFO Starting job - plot_energy_volume_curve_job (4bb64989-7cc3-4dc4-900f-19c10d959dfa)\n", + "2024-07-03 14:01:06,497 INFO Finished job - plot_energy_volume_curve_job (4bb64989-7cc3-4dc4-900f-19c10d959dfa)\n", + "2024-07-03 14:01:06,498 INFO Finished executing jobs locally\n" ] }, { "data": { - "text/html": "", - "text/plain": "" + "text/html": [ + "" + ], + "text/plain": [ + "" + ] }, "metadata": {}, "output_type": "display_data" }, { "data": { - "text/plain": "
", - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAksAAAHACAYAAACyIiyEAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAABZKElEQVR4nO3deVxVdeL/8de9LJd9UUFERXAFN3LPpabSzCzNJc0yS7NtZmwms1Ira2oqrbRpapqW+TaoWVOZLZZlaloZ4pp7iIoiKosLsgiy3vP7Q+UXqYgI93Av7+fjcR+P4XDO5X3PEPft53zu51gMwzAQERERkfOymh1AREREpC5TWRIRERGphMqSiIiISCVUlkREREQqobIkIiIiUgmVJREREZFKqCyJiIiIVEJlSURERKQSKksiIiIilVBZEhEREamEypJJXnjhBfr06YOPjw9BQUFVOsYwDJ5++mmaNGmCt7c3AwYMYM+ePeXf/+GHH7BYLOd9bNiw4Zzn27t3L/7+/lX++b81dOhQIiIi8PLyokmTJowbN460tLRLfh4REZG6TmXJJMXFxYwaNYo//vGPVT7m5Zdf5vXXX+ftt99m3bp1+Pr6csMNN1BYWAhAnz59SE9Pr/C49957iYqKonv37hWeq6SkhNtvv52rrrqqWvmvvfZaPvnkE5KSkli0aBHJycnceuut1XouERGRusyiG+maa+7cuTz88MNkZ2dXup9hGISHhzNlyhQeffRRAHJycmjcuDFz585lzJgx5xxTUlJC06ZNeeihh5gxY0aF702dOpW0tDT69+9/3p//f//3f8yZM4f9+/cTGRnJX/7yF/70pz9dMN/ixYsZNmwYRUVFeHh4VO3Fi4iIOAGNLDmJ/fv3k5GRwYABA8q3BQYG0qtXLxISEs57zOLFizl+/DgTJkyosH3lypUsXLiQN99887zHffDBBzz99NO88MILJCYm8uKLLzJjxgzmzZt33v2zsrL44IMP6NOnj4qSiIi4HJUlJ5GRkQFA48aNK2xv3Lhx+fd+77333uOGG26gWbNm5duOHz/O+PHjmTt3LgEBAec97plnnmHOnDmMGDGCqKgoRowYweTJk3nnnXcq7Dd16lR8fX1p2LAhqampfPnll5fzEkVEROoklaUaNG3atAtOsD772LVrl0OyHDp0iO+++46JEydW2H7fffdxxx13cPXVV5/3uPz8fJKTk5k4cSJ+fn7lj+eff57k5OQK+z722GNs3ryZZcuW4ebmxl133YWu6oqIiKtxNzuAK5kyZQrjx4+vdJ+WLVtW67nDwsIAyMzMpEmTJuXbMzMzueKKK87ZPy4ujoYNGzJ06NAK21euXMnixYuZPXs2cHoulN1ux93dnXfffZebbroJgP/85z/06tWrwrFubm4Vvm7UqBGNGjWibdu2xMTE0Lx5c9auXUvv3r2r9RpFRETqIpWlGhQSEkJISEitPHdUVBRhYWF8//335eUoNzeXdevWnfOJOsMwiIuL46677jpnDlFCQgJlZWXlX3/55Ze89NJLrFmzhqZNmxIcHEx4eDj79u1j7NixVc5nt9sBKCoqquYrFBERqZtUlkySmppKVlYWqamplJWVsWXLFgBat26Nn58fANHR0cycOZPhw4djsVh4+OGHef7552nTpg1RUVHMmDGD8PBwhg0bVuG5V65cyf79+7n33nvP+bkxMTEVvt64cSNWq5WOHTuWb3v22Wf5y1/+QmBgIIMGDaKoqIiNGzdy4sQJHnnkEdatW8eGDRvo168fwcHBJCcnM2PGDFq1aqVRJRERcTkqSyZ5+umnK3y6rEuXLgCsWrWKa665BoCkpCRycnLK93n88cfJz8/n/vvvJzs7m379+rF06VK8vLwqPPd7771Hnz59iI6Orla2e++9Fx8fH1555RUee+wxfH196dSpEw8//DAAPj4+fPbZZzzzzDPk5+fTpEkTBg0axFNPPYXNZqvWzxQREamrtM6SiIiISCX0aTgRERGRSqgsiYiIiFRCc5ZqgN1uJy0tDX9/fywWi9lxREREpAoMwyAvL4/w8HCs1guPH6ks1YC0tDSaN29udgwRERGphoMHD1a428XvqSzVAH9/f+D0yb7QLURERESkbsnNzaV58+bl7+MXorJUA85eegsICFBZEhERcTIXm0KjCd4iIiIilVBZEhEREamEypKIiIhIJVSWRERERCqhsiQiIiJSCZUlERERkUqoLImIiIhUQmVJREREpBIqSyIiIiKVUFkSERERqYTKkoiIiEglVJZEREREKqGyVIfZ7Qar9xzFMAyzo4iIiNRbKkt1lGEYDH3zZ8a9t57Ve46ZHUdERKTeUlmqoywWC91bNAAgLn6/yWlERETqL5WlOmx8n0gsFliVdJR9R0+a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" + "text/plain": [ + "
" + ], + "image/png": "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" }, "metadata": {}, "output_type": "display_data" } ], - "source": [ - "from jobflow import job, Response, Flow, run_locally\n", - "\n", - "# set up QE PP env\n", - "os.environ[\"ESPRESSO_PSEUDO\"] = f\"{os.getcwd()}/espresso/pseudo\"\n", - "\n", - "\n", - "@job\n", - "def get_ev_curve(structure, strain_lst):\n", - " structures = generate_structures(structure, strain_lst=strain_lst)\n", - " jobs = []\n", - " volumes = []\n", - " energies = []\n", - " for istructure in range(len(strain_lst)):\n", - " new_job = StaticQEMaker().make(structures[istructure])\n", - " jobs.append(new_job)\n", - " volumes.append(new_job.output.volume)\n", - " energies.append(new_job.output.energy)\n", - " return Response(\n", - " replace=Flow(jobs, output={\"energies\": energies, \"volumes\": volumes})\n", - " )\n", - "\n", - "\n", - "@job\n", - "def plot_energy_volume_curve_job(volume_lst, energy_lst):\n", - " plot_energy_volume_curve(volume_lst=volume_lst, energy_lst=energy_lst)\n", - "\n", - "\n", - "structure = bulk(\"Al\", a=4.15, cubic=True)\n", - "relax = BaseQEMaker().make(structure=MSONAtoms(structure))\n", - "ev_curve_data = get_ev_curve(\n", - " relax.output.structure, strain_lst=np.linspace(0.9, 1.1, 5)\n", - ")\n", - "# structure optimization job and (E, V) curve data job connected via relax.output\n", - "plot_curve = plot_energy_volume_curve_job(\n", - " volume_lst=ev_curve_data.output[\"volumes\"],\n", - " energy_lst=ev_curve_data.output[\"energies\"],\n", - ")\n", - "# (E, V) curve data job and plotting the curve job connected via ev_curve_data.output\n", - "qe_flow = [relax, ev_curve_data, plot_curve]\n", - "qe_fw = Flow(qe_flow)\n", - "# qe_flow is the QE flow that consists of the job for structural optimization, calculating the (E, V) curve data points and plotting the curve\n", - "run_locally(\n", - " qe_fw, create_folders=True\n", - ") # order of the jobs in the flow is determined by connectivity\n", - "\n", - "graph = to_mermaid(qe_fw, show_flow_boxes=True)\n", - "mm(graph)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-06-30T21:49:54.258057729Z", - "start_time": "2024-06-30T21:49:02.701155072Z" - } - } + "execution_count": 15 }, { "cell_type": "markdown", @@ -834,17 +834,6 @@ }, { "cell_type": "code", - "execution_count": 21, - "outputs": [ - { - "data": { - "text/html": "", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ "mm(\n", " \"\"\"\n", @@ -857,10 +846,25 @@ "metadata": { "collapsed": false, "ExecuteTime": { - "end_time": "2024-06-30T21:52:49.647846141Z", - "start_time": "2024-06-30T21:52:49.604021289Z" + "end_time": "2024-07-03T12:01:06.592117Z", + "start_time": "2024-07-03T12:01:06.587277Z" } - } + }, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 16 }, { "metadata": { @@ -898,20 +902,11 @@ "cell_type": "markdown", "source": [ "## Publication of the workflow\n", - "The `jobflow` infrastructure does not provide a dedicated platform for publishing a workflow currently. However, workflows related to computational materials science have been collected in the package `atomate2`. In addition, users can build their own package by relying on jobflow and share it as any Python project." + "The `jobflow` infrastructure does not provide a dedicated platform for publishing a workflow currently. However, workflows related to computational materials science have been collected in the package `atomate2`. In addition, users can build their own package by relying on jobflow and share it as a new Python-based program. Additional packages in materials science using `jobflow` exist." ], "metadata": { "collapsed": false } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false - } } ] } From 73b06f691cd26971495269446f08f60b222be61d Mon Sep 17 00:00:00 2001 From: JaGeo Date: Wed, 3 Jul 2024 15:17:35 +0200 Subject: [PATCH 3/5] reformatting --- jobflow.ipynb | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/jobflow.ipynb b/jobflow.ipynb index 3e7f9eb..8a5ce20 100644 --- a/jobflow.ipynb +++ b/jobflow.ipynb @@ -64,7 +64,7 @@ "\n", "\n", "@job\n", - "def my_function(my_parameter: int)->int:\n", + "def my_function(my_parameter: int) -> int:\n", " return my_parameter\n", "\n", "\n", @@ -91,12 +91,12 @@ "\n", "\n", "@job\n", - "def my_function(my_parameter: int)->int:\n", + "def my_function(my_parameter: int) -> int:\n", " return my_parameter\n", "\n", "\n", "@job\n", - "def my_second_function(my_parameter: int)->int:\n", + "def my_second_function(my_parameter: int) -> int:\n", " return my_parameter\n", "\n", "\n", @@ -182,7 +182,7 @@ " scaling: float = 1.1\n", "\n", " @job\n", - " def make(self, my_parameter: int)->float:\n", + " def make(self, my_parameter: int) -> float:\n", " return my_parameter * self.scaling\n", "\n", "\n", @@ -322,8 +322,12 @@ "source": [ "from pymatgen.io.ase import MSONAtoms\n", "from ase import Atoms\n", - "def generate_structures(structure: Atoms, strain_lst: list(float)): # structure should be of ase Atoms type\n", - " structure=MSONAtoms(structure)\n", + "\n", + "\n", + "def generate_structures(\n", + " structure: Atoms, strain_lst: list(float)\n", + "): # structure should be of ase Atoms type\n", + " structure = MSONAtoms(structure)\n", " structure_lst = []\n", " for strain in strain_lst:\n", " structure_strain = structure.copy()\n", @@ -354,7 +358,7 @@ { "cell_type": "code", "source": [ - "def plot_energy_volume_curve(volume_lst: list(float), energy_lst:list(float)):\n", + "def plot_energy_volume_curve(volume_lst: list(float), energy_lst: list(float)):\n", " plt.plot(volume_lst, energy_lst)\n", " plt.xlabel(\"Volume\")\n", " plt.ylabel(\"Energy\")\n", @@ -381,7 +385,7 @@ { "cell_type": "code", "source": [ - "def write_input(input_dict: dict, working_directory: str=\".\"):\n", + "def write_input(input_dict: dict, working_directory: str = \".\"):\n", " filename = os.path.join(working_directory, \"input.pwi\")\n", " os.makedirs(working_directory, exist_ok=True)\n", " write(\n", @@ -500,8 +504,10 @@ "\n", "\n", "def write_qe_input_set(\n", - " structure: Atoms, input_set_generator: InputGenerator=QEInputGenerator(), working_directory: str=\".\"\n", - ")->None:\n", + " structure: Atoms,\n", + " input_set_generator: InputGenerator = QEInputGenerator(),\n", + " working_directory: str = \".\",\n", + ") -> None:\n", " qis = input_set_generator.get_input_set(structure=structure)\n", " qis.write_input(working_directory=working_directory)" ], @@ -535,7 +541,7 @@ "QE_CMD = \"mpirun -np 1 pw.x -in input.pwi > output.pwo\"\n", "\n", "\n", - "def run_qe(qe_cmd: str=QE_CMD):\n", + "def run_qe(qe_cmd: str = QE_CMD):\n", " subprocess.check_output(qe_cmd, shell=True, universal_newlines=True)\n", "\n", "\n", @@ -572,7 +578,7 @@ " input_set_generator: QEInputGenerator = field(default_factory=QEInputGenerator)\n", "\n", " @job(output_schema=QETaskDoc)\n", - " def make(self, structure: Atoms|MSONAtoms) -> QETaskDoc:\n", + " def make(self, structure: Atoms | MSONAtoms) -> QETaskDoc:\n", " \"\"\"\n", " Run a QE calculation.\n", "\n", @@ -645,7 +651,7 @@ "\n", "\n", "@job\n", - "def get_ev_curve(structure: Atoms|MSONAtoms, strain_lst: list(float)):\n", + "def get_ev_curve(structure: Atoms | MSONAtoms, strain_lst: list(float)):\n", " structures = generate_structures(structure, strain_lst=strain_lst)\n", " jobs = []\n", " volumes = []\n", From 5f3c2000579d530476ca7815f722e89a0dd64a82 Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Sun, 7 Jul 2024 18:15:51 +0200 Subject: [PATCH 4/5] a small revision --- jobflow.ipynb | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/jobflow.ipynb b/jobflow.ipynb index 8a5ce20..cd5a238 100644 --- a/jobflow.ipynb +++ b/jobflow.ipynb @@ -31,7 +31,9 @@ { "metadata": {}, "cell_type": "markdown", - "source": "[`jobflow`](https://materialsproject.github.io/jobflow/index.html) and [`atomate2`](https://materialsproject.github.io/atomate2/index.html) are key packages of the [Materials Project](https://materialsproject.org/) . `jobflow` was especially designed to simplify the execution of dynamic workflows -- when the actual number of jobs is dynamically determined upon runtime instead of being statically fixed before running the workflow(s). `jobflow`'s overall flexibility allows for building workflows that go beyond the usage in materials science. `jobflow` forms the basis of `atomate2`. `atomate2` implements data generation workflows in the context of materials science and will be responsible for data generation in the Materials Project in the future. " + "source": [ + "[`jobflow`](https://materialsproject.github.io/jobflow/index.html) and [`atomate2`](https://materialsproject.github.io/atomate2/index.html) are key packages of the [Materials Project](https://materialsproject.org/) . `jobflow` was especially designed to simplify the execution of dynamic workflows -- when the actual number of jobs is dynamically determined upon runtime instead of being statically fixed before running the workflow(s). `jobflow`'s overall flexibility allows for building workflows that go beyond the usage in materials science. `jobflow` serves as the basis of `atomate2`, which implements data generation workflows in the context of materials science and will be used for data generation in the Materials Project in the future. " + ] }, { "metadata": {}, @@ -39,7 +41,7 @@ "source": [ "## Installation / Setup\n", "`jobflow` can be installed via `pip install` and directly run with the default setup. It will then internally rely on a memory database as defined in the software package [`maggma`](https://materialsproject.github.io/maggma/). For large-scale usage, a [MongoDB](https://www.mongodb.com/)-like database might be specified in a jobflow.yaml file.\n", - "A high-throughput setup (i.e., parallel execution of independent parts in the workflow) of `jobflow` can be achieved using additional packages like [`fireworks`](https://materialsproject.github.io/fireworks/) or [`jobflow-remote`](https://matgenix.github.io/jobflow-remote/). Both packages require a MongoDB database. In case of `FireWorks`, however, the MongoDB database needs to be directly connected to the compute nodes. `jobflow-remote` allows remote submission options that only require a MongoDB database on the submitting computer but not the compute nodes. It can also deal with multi-factor authentification." + "A high-throughput setup (i.e., parallel execution of independent parts in the workflow) of `jobflow` can be achieved using additional packages like [`fireworks`](https://materialsproject.github.io/fireworks/) or [`jobflow-remote`](https://matgenix.github.io/jobflow-remote/). Both packages require a MongoDB database. In case of `FireWorks`, however, the MongoDB database needs to be directly connected to the compute nodes. `jobflow-remote` allows remote submission options that only require a MongoDB database on the submitting computer but not the compute nodes. It can also deal with multi-factor authentication." ] }, { @@ -240,7 +242,9 @@ }, { "cell_type": "markdown", - "source": "Then, we import tools for data plotting and mathematical operations and manipulation.", + "source": [ + "Then, we import tools for data plotting as well as mathematical operations and manipulation." + ], "metadata": { "collapsed": false, "ExecuteTime": { @@ -325,7 +329,7 @@ "\n", "\n", "def generate_structures(\n", - " structure: Atoms, strain_lst: list(float)\n", + " structure: Atoms, strain_lst: list[float]\n", "): # structure should be of ase Atoms type\n", " structure = MSONAtoms(structure)\n", " structure_lst = []\n", @@ -358,7 +362,7 @@ { "cell_type": "code", "source": [ - "def plot_energy_volume_curve(volume_lst: list(float), energy_lst: list(float)):\n", + "def plot_energy_volume_curve(volume_lst: list[float], energy_lst: list[float]):\n", " plt.plot(volume_lst, energy_lst)\n", " plt.xlabel(\"Volume\")\n", " plt.ylabel(\"Energy\")\n", @@ -524,7 +528,7 @@ { "cell_type": "markdown", "source": [ - "The next steps are all concerned about handling the execution of QE and the output data collection. We start by defining a QE task document `QETaskDoc` to systematically collect the output data. For the (E, V) curve, the energy and volume are of course the most important information. The task document could be further extended to contain information that is relevant for other purposes. Next, we define a `BaseQEMaker` to handle generic QE jobs (in our case for the structural relaxation) and a separate `StaticQEMaker` for the static QE calculations. The `BaseQEMaker` is expecting the generic input set generated by `QEInputGenerator`, while `StaticQEMaker` expects the `QEInputStaticGenerator` type. As the `StaticQEMaker` inherits from the `BaseQEMaker`, we only need to make sure to pass the correct input set generator type." + "The next steps are all about handling the execution of QE and the output data collection. We start by defining a QE task document `QETaskDoc` to systematically collect the output data. For the (E, V) curve, the energy and volume are of course the most important information. The task document could be further extended to contain other relevant information. Next, we define a `BaseQEMaker` to handle generic QE jobs (in our case for the structural relaxation) and a separate `StaticQEMaker` for the static QE calculations. The `BaseQEMaker` is expecting the generic input set generated by `QEInputGenerator`, while `StaticQEMaker` expects the `QEInputStaticGenerator` type. As the `StaticQEMaker` inherits from the `BaseQEMaker`, we only need to make sure to pass the correct input set generator type." ], "metadata": { "collapsed": false @@ -636,7 +640,9 @@ }, { "cell_type": "markdown", - "source": "Finally, it's time to orchestrate all functions and classes together in an actual flow. Note how the number of jobs in `get_ev_curve` can be flexibly controlled by using `strain_lst` and therefore we use a `Response` object to handle the flexible job output. By making `get_ev_curve` and `plot_energy_volume_curve_job` into `job` objects using the `@job` decorator, we ensure that first all the (E, V) data points are calculated before they are plotted. The `qe_flow` contains the list of the jobs that need to be executed in this workflow. The jobs are connected by the respective `job.output` objects that also ensures the correct order in executing the jobs.", + "source": [ + "Finally, it's time to orchestrate all functions and classes together into an actual flow. Note how the number of jobs in `get_ev_curve` can be flexibly controlled by using `strain_lst` and therefore we use a `Response` object to handle the flexible job output. By making `get_ev_curve` and `plot_energy_volume_curve_job` into `job` objects using the `@job` decorator, we ensure that first all the (E, V) data points are calculated before they are plotted. The `qe_flow` contains the list of the jobs that need to be executed in this workflow. The jobs are connected by the respective `job.output` objects that also ensures the correct order in executing the jobs." + ], "metadata": { "collapsed": false } @@ -687,7 +693,7 @@ "# qe_flow is the QE flow that consists of the job for structural optimization, calculating the (E, V) curve data points and plotting the curve\n", "run_locally(\n", " qe_fw, create_folders=True\n", - ") # order of the jobs in the flow is determined by connectivity\n", + ") # order of the jobs in the flow determined by connectivity\n", "\n", "graph = to_mermaid(qe_fw, show_flow_boxes=True)\n", "mm(graph)" @@ -908,11 +914,20 @@ "cell_type": "markdown", "source": [ "## Publication of the workflow\n", - "The `jobflow` infrastructure does not provide a dedicated platform for publishing a workflow currently. However, workflows related to computational materials science have been collected in the package `atomate2`. In addition, users can build their own package by relying on jobflow and share it as a new Python-based program. Additional packages in materials science using `jobflow` exist." + "The `jobflow` infrastructure does not provide a dedicated platform for publishing a workflow currently. However, workflows related to computational materials science have been collected in the package `atomate2`. In addition, users can build their own package by relying on `jobflow` and share it as a new Python-based program. There are also additional materials science packages that rely on `jobflow`." ], "metadata": { "collapsed": false } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false + } } ] } From 68dc0518fd146f7c8ca4bf670e4e4028ecb90da2 Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Sun, 7 Jul 2024 18:51:59 +0200 Subject: [PATCH 5/5] giving some examples fo outside MP use of jobflow --- jobflow.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobflow.ipynb b/jobflow.ipynb index cd5a238..5de51ee 100644 --- a/jobflow.ipynb +++ b/jobflow.ipynb @@ -914,7 +914,7 @@ "cell_type": "markdown", "source": [ "## Publication of the workflow\n", - "The `jobflow` infrastructure does not provide a dedicated platform for publishing a workflow currently. However, workflows related to computational materials science have been collected in the package `atomate2`. In addition, users can build their own package by relying on `jobflow` and share it as a new Python-based program. There are also additional materials science packages that rely on `jobflow`." + "The `jobflow` infrastructure does not provide a dedicated platform for publishing a workflow currently. However, workflows related to computational materials science have been collected in the package `atomate2`. In addition, users can build their own package by relying on `jobflow` and share it as a new Python-based program. There are also additional materials science packages like [NanoParticleTools](https://github.com/BlauGroup/NanoParticleTools) or [QuAcc](https://github.com/Quantum-Accelerators/quacc) that rely on `jobflow`." ], "metadata": { "collapsed": false