diff --git a/binder/environment.yml b/binder/environment.yml index fc162be..90c8420 100644 --- a/binder/environment.yml +++ b/binder/environment.yml @@ -11,7 +11,9 @@ dependencies: - pyiron_base =0.15.19 - pyiron_workflow =0.18.0 - pygraphviz =1.14 -- aiida-workgraph =0.9.0 +- pip +- pip: + - git+https://github.com/aiidateam/aiida-core.git@8c24cbc94dd58f2bb36e70402b51f4124fc90edb - conda_subprocess =0.0.12 - networkx =3.6.1 - cwltool =3.2.20260413085819 diff --git a/documentation/intro.md b/documentation/intro.md index 4ad9228..c8165f8 100644 --- a/documentation/intro.md +++ b/documentation/intro.md @@ -10,7 +10,7 @@ different workflow engines to enable interoperability. ## Workflow Engines Currently supported workflow engines: -* [aiida-workgraph](https://github.com/aiidateam/aiida-workgraph) +* [aiida-core](https://github.com/aiidateam/aiida-core) * [jobflow](https://github.com/materialsproject/jobflow) * [pyiron_base](https://github.com/pyiron/pyiron_base) * [pyiron_workflow](https://github.com/pyiron/pyiron_workflow) diff --git a/example_workflows/arithmetic/aiida.ipynb b/example_workflows/arithmetic/aiida.ipynb index bd067d8..d9a9311 100644 --- a/example_workflows/arithmetic/aiida.ipynb +++ b/example_workflows/arithmetic/aiida.ipynb @@ -23,12 +23,12 @@ "cells": [ { "cell_type": "markdown", - "source": "# Aiida\n\nThis notebook defines the arithmetic workflow with [`aiida-workgraph`](https://github.com/aiidateam/aiida-workgraph) and then loads the resulting `workflow.json` into `jobflow`, `pyiron_base` and `pyiron_workflow`, to demonstrate that the same workflow definition can be executed by several different engines.", + "source": "# Aiida\n\nThis notebook defines the arithmetic workflow with [`aiida-core`](https://github.com/aiidateam/aiida-core) and then loads the resulting `workflow.json` into `jobflow`, `pyiron_base` and `pyiron_workflow`, to demonstrate that the same workflow definition can be executed by several different engines.", "metadata": {} }, { "cell_type": "markdown", - "source": "## Define workflow with aiida\n\n`aiida-workgraph` represents a workflow as a `WorkGraph` of `Task`s. Tasks are executed and stored by the AiiDA engine, so we first need to connect to an AiiDA profile with `load_profile()` before any task can run.", + "source": "## Define workflow with aiida\n\n`aiida-core` represents a workflow as a `GraphHandle` of `@task`s. Tasks are executed and stored by the AiiDA engine, so we first need to connect to an AiiDA profile with `load_profile()` before any task can run.", "metadata": {} }, { @@ -36,87 +36,90 @@ "source": [ "from python_workflow_definition.aiida import write_workflow_json\n", "\n", - "from aiida_workgraph import WorkGraph, task\n", - "from aiida import orm, load_profile\n", - "load_profile()\n", + "from aiida.engine import graph, task\n", "\n", - "workflow_json_filename = \"aiida_simple.json\"" + "workflow_json_filename = \"aiida_simple.json\"" ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 1 + "execution_count": null }, { "cell_type": "code", "source": [ + "import typing as t\n", + "from typing import Any\n", + "\n", "from workflow import (\n", " get_sum as _get_sum,\n", " get_prod_and_div as _get_prod_and_div,\n", " get_square as _get_square,\n", - ")" + ")\n", + "\n", + "\n", + "class ProdDiv(t.TypedDict):\n", + " prod: Any\n", + " div: Any\n", + "\n", + "\n", + "def get_prod_and_div(x, y) -> ProdDiv:\n", + " return _get_prod_and_div(x, y)\n", + "\n", + "\n", + "get_prod_and_div.__module__ = \"workflow\"\n", + "get_prod_and_div = task(get_prod_and_div)\n", + "\n", + "\n", + "def get_sum(x, y) -> Any:\n", + " return _get_sum(x, y)\n", + "\n", + "\n", + "get_sum.__module__ = \"workflow\"\n", + "get_sum = task(get_sum)\n", + "\n", + "\n", + "def get_square(x) -> Any:\n", + " return _get_square(x)\n", + "\n", + "\n", + "get_square.__module__ = \"workflow\"\n", + "get_square = task(get_square)" ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 2 + "execution_count": null }, { "cell_type": "markdown", - "source": "The arithmetic functions are imported under a leading underscore because they are still plain Python functions at this point; `wg.add_task` below turns each one into an AiiDA `Task` that can be linked to other tasks' inputs and outputs. `get_prod_and_div` returns a dictionary, so it is wrapped with `task(outputs=['prod', 'div'])` to expose `prod` and `div` as separate output sockets.", + "source": "The arithmetic functions are imported under a leading underscore and wrapped with `@task` below, turning each plain function into a task that can be linked to other tasks. `get_prod_and_div` returns a dictionary, so its wrapper declares a `ProdDiv` `TypedDict` to expose `prod` and `div` as separate outputs.", "metadata": {} }, { "cell_type": "code", - "source": "wg = WorkGraph(\"arithmetic\")", - "metadata": { - "trusted": true - }, - "outputs": [], - "execution_count": 3 - }, - { - "cell_type": "code", - "source": "get_prod_and_div_task = wg.add_task(\n task(outputs=['prod', 'div'])(_get_prod_and_div),\n x=orm.Float(1),\n y=orm.Float(2),\n)", - "metadata": { - "trusted": true - }, - "outputs": [], - "execution_count": 4 - }, - { - "cell_type": "code", - "source": "get_sum_task = wg.add_task(\n _get_sum,\n x=get_prod_and_div_task.outputs.prod,\n y=get_prod_and_div_task.outputs.div,\n)", - "metadata": { - "trusted": true - }, - "outputs": [], - "execution_count": 5 - }, - { - "cell_type": "code", - "source": "get_square_task = wg.add_task(\n _get_square,\n x=get_sum_task.outputs.result,\n)", + "source": "@graph\ndef arithmetic(x=1, y=2):\n prod_div = get_prod_and_div(x=x, y=y)\n total = get_sum(x=prod_div[\"prod\"], y=prod_div[\"div\"])\n return {\"result\": get_square(x=total)}", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 6 + "execution_count": null }, { "cell_type": "markdown", - "source": "With all tasks connected, `write_workflow_json` walks the `WorkGraph`'s tasks and links and serializes them into the PWD `workflow.json` format: each task becomes a `function` node and each link becomes an edge between an input and an output port.", + "source": "With all tasks connected, `write_workflow_json` walks the `GraphHandle` tasks and links and serializes them into the PWD `workflow.json` format: each task becomes a `function` node and each link becomes an edge between an input and an output port.", "metadata": {} }, { "cell_type": "code", - "source": "write_workflow_json(wg=wg, file_name=workflow_json_filename)", + "source": "write_workflow_json(arithmetic, workflow_json_filename)", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 7 + "execution_count": null }, { "cell_type": "code", @@ -246,7 +249,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "## Load Workflow with pyiron_workflow\n\nFinally, the workflow is loaded into `pyiron_workflow`, producing a `Workflow` object whose node graph can be visualized with `.draw()` and executed with `.run()`." + "source": "## Load Workflow with pyiron_workflow\n\nFinally, the workflow is loaded into `pyiron_workflow`, producing a `Workflow` object whose node graph can be visualized with `.draw()` and executed with `run_get_node`." }, { "metadata": {}, diff --git a/example_workflows/arithmetic/executorlib.ipynb b/example_workflows/arithmetic/executorlib.ipynb index 612c615..5605136 100644 --- a/example_workflows/arithmetic/executorlib.ipynb +++ b/example_workflows/arithmetic/executorlib.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "id": "c39b76fb-259f-4e16-a44d-02a295c82386", "metadata": {}, - "source": "# executorlib\n\nThis notebook defines the arithmetic workflow with [`executorlib`](https://github.com/pyiron/executorlib), a lightweight `concurrent.futures`-style executor for up-scaling Python functions to HPC resources, and then loads the resulting `workflow.json` into `aiida-workgraph`, `jobflow`, `pyiron_base` and `pyiron_workflow`." + "source": "# executorlib\n\nThis notebook defines the arithmetic workflow with [`executorlib`](https://github.com/pyiron/executorlib), a lightweight `concurrent.futures`-style executor for up-scaling Python functions to HPC resources, and then loads the resulting `workflow.json` into `aiida-core`, `jobflow`, `pyiron_base` and `pyiron_workflow`." }, { "cell_type": "markdown", @@ -159,34 +159,27 @@ "cell_type": "markdown", "id": "a4c0faaf-e30d-4ded-8e9f-57f97f51b14c", "metadata": {}, - "source": "## Load Workflow with aiida\n\nThe exported `workflow.json` is loaded into `aiida-workgraph`. As with the other notebooks, an AiiDA profile must be connected via `load_profile()` before the resulting `WorkGraph` can be run." + "source": "## Load Workflow with aiida\n\nThe exported `workflow.json` is loaded into `aiida-core`. As with the other notebooks, an AiiDA profile must be connected via `load_profile()` before the resulting `GraphHandle` can be run." }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "2ecc229f-daa1-49b9-9279-a6b5ae1aa4f2", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Profile" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ + "import json\n", + "\n", "from aiida import load_profile\n", "\n", + "from aiida.engine.launch import run_get_node\n", + "\n", "load_profile()" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "68a56b32-9f99-43d7-aaee-0c1cd9522681", "metadata": {}, "outputs": [], @@ -196,64 +189,25 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "8f2a621d-b533-4ddd-8bcd-c22db2f922ec", "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d1e4dac30bd3409f8000d40062e2ae1b", - "version_major": 2, - "version_minor": 1 - }, - "text/plain": [ - "" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "wg = load_workflow_json(file_name=workflow_json_filename)\n", - "wg" + "graph = load_workflow_json(file_name=workflow_json_filename)" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "cf80267d-c2b0-4236-bf1d-a57596985fc1", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "01/16/2026 08:08:17 AM <39822> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1573|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_prod_and_div\n", - "01/16/2026 08:08:17 AM <39822> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1573|WorkGraphEngine|update_task_state]: Task: get_prod_and_div, type: PYFUNCTION, finished.\n", - "01/16/2026 08:08:17 AM <39822> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1573|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_sum\n", - "01/16/2026 08:08:18 AM <39822> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1573|WorkGraphEngine|update_task_state]: Task: get_sum, type: PYFUNCTION, finished.\n", - "01/16/2026 08:08:18 AM <39822> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1573|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_square\n", - "01/16/2026 08:08:18 AM <39822> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1573|WorkGraphEngine|update_task_state]: Task: get_square, type: PYFUNCTION, finished.\n", - "01/16/2026 08:08:18 AM <39822> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1573|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n", - "01/16/2026 08:08:18 AM <39822> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1573|WorkGraphEngine|finalize]: Finalize workgraph.\n" - ] - }, - { - "data": { - "text/plain": [ - "{}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "wg.run()" + "with open(workflow_json_filename) as _f:\n", + " inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { @@ -491,7 +445,7 @@ "cell_type": "markdown", "id": "406d62d0-76eb-411f-a7fb-f866d5cec9c1", "metadata": {}, - "source": "## Load Workflow with pyiron_workflow\n\nFinally, the workflow is loaded into a `pyiron_workflow` `Workflow` object, drawn with `.draw()` and executed with `.run()`." + "source": "## Load Workflow with pyiron_workflow\n\nFinally, the workflow is loaded into a `pyiron_workflow` `Workflow` object, drawn with `.draw()` and executed with `run_get_node`." }, { "cell_type": "code", diff --git a/example_workflows/arithmetic/jobflow.ipynb b/example_workflows/arithmetic/jobflow.ipynb index 3ff3b1c..dbe40da 100644 --- a/example_workflows/arithmetic/jobflow.ipynb +++ b/example_workflows/arithmetic/jobflow.ipynb @@ -207,69 +207,46 @@ "source": "## Load Workflow with aiida\n\nIn this part, we will demonstrate how to import the `jobflow` workflow into `aiida` via the PWD.", "metadata": {} }, + { + "id": "cc7127193d31d8ef", + "cell_type": "markdown", + "source": "We import the necessary modules from `aiida` and the PWD, as well as the workflow JSON file.", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + } + }, { "id": "66a1b3a6-3d3b-4caa-b58f-d8bc089b1074", "cell_type": "code", - "source": "from aiida import load_profile\n\nload_profile()", + "source": "import json\n\nfrom aiida import load_profile\n\nfrom aiida.engine.launch import run_get_node\n\nload_profile()", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 12, - "output_type": "execute_result", - "data": { - "text/plain": "Profile" - }, - "metadata": {} - } - ], - "execution_count": 12 + "outputs": [], + "execution_count": null }, { "id": "4679693b-039b-45cf-8c67-5b2b3d705a83", "cell_type": "code", - "source": "from python_workflow_definition.aiida import load_workflow_json", + "source": "from python_workflow_definition.aiida import load_workflow_json\nworkflow_json_filename = \"aiida_simple.json\"", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 13 - }, - { - "id": "cc7127193d31d8ef", - "cell_type": "markdown", - "source": "We import the necessary modules from `aiida` and the PWD, as well as the workflow JSON file.", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - } + "execution_count": null }, { "id": "68c41a61-d185-47e8-ba31-eeff71d8b2c6", "cell_type": "code", - "source": "wg = load_workflow_json(file_name=workflow_json_filename)\nwg", + "source": "graph = load_workflow_json(file_name=workflow_json_filename)", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 14, - "output_type": "execute_result", - "data": { - "text/plain": "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'WorkGra…", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 1, - "model_id": "9f822f7ac5ca4cb9960374af471f79cf" - } - }, - "metadata": {} - } - ], - "execution_count": 14 + "outputs": [], + "execution_count": null }, { "id": "4816325767559bbe", @@ -285,18 +262,12 @@ { "id": "05228ece-643c-420c-8df8-4ce3df379515", "cell_type": "code", - "source": "wg.run()", + "source": "with open(workflow_json_filename) as _f:\n inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\noutputs, _ = run_get_node(graph, **inputs)\n{k: v.value for k, v in outputs.items()}", "metadata": { "trusted": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": "05/24/2025 05:38:40 AM <306> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_prod_and_div1\n05/24/2025 05:38:41 AM <306> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|update_task_state]: Task: get_prod_and_div1, type: PyFunction, finished.\n05/24/2025 05:38:41 AM <306> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_sum2\n05/24/2025 05:38:41 AM <306> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|update_task_state]: Task: get_sum2, type: PyFunction, finished.\n05/24/2025 05:38:41 AM <306> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_square3\n05/24/2025 05:38:42 AM <306> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|update_task_state]: Task: get_square3, type: PyFunction, finished.\n05/24/2025 05:38:42 AM <306> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n05/24/2025 05:38:42 AM <306> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|finalize]: Finalize workgraph.\n" - } - ], - "execution_count": 15 + "outputs": [], + "execution_count": null }, { "id": "2c942094-61b4-4e94-859a-64f87b5bec64", diff --git a/example_workflows/arithmetic/pyiron_base.ipynb b/example_workflows/arithmetic/pyiron_base.ipynb index eaba0a5..2f52d8a 100644 --- a/example_workflows/arithmetic/pyiron_base.ipynb +++ b/example_workflows/arithmetic/pyiron_base.ipynb @@ -24,7 +24,7 @@ { "id": "c39b76fb-259f-4e16-a44d-02a295c82386", "cell_type": "markdown", - "source": "# pyiron\n\nThis notebook defines the arithmetic workflow with [`pyiron_base`](https://github.com/pyiron/pyiron_base) and then loads the resulting `workflow.json` into `aiida-workgraph`, `jobflow` and `pyiron_workflow`.", + "source": "# pyiron\n\nThis notebook defines the arithmetic workflow with [`pyiron_base`](https://github.com/pyiron/pyiron_base) and then loads the resulting `workflow.json` into `aiida-core`, `jobflow` and `pyiron_workflow`.", "metadata": {} }, { @@ -148,27 +148,18 @@ { "id": "a4c0faaf-e30d-4ded-8e9f-57f97f51b14c", "cell_type": "markdown", - "source": "## Load Workflow with aiida\n\nThe exported `workflow.json` is loaded into `aiida-workgraph`. An AiiDA profile must be connected via `load_profile()` before the resulting `WorkGraph` can be run.", + "source": "## Load Workflow with aiida\n\nThe exported `workflow.json` is loaded into `aiida-core`. An AiiDA profile must be connected via `load_profile()` before the resulting `GraphHandle` can be run.", "metadata": {} }, { "id": "2ecc229f-daa1-49b9-9279-a6b5ae1aa4f2", "cell_type": "code", - "source": "from aiida import load_profile\n\nload_profile()", + "source": "import json\n\nfrom aiida import load_profile\n\nfrom aiida.engine.launch import run_get_node\n\nload_profile()", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 11, - "output_type": "execute_result", - "data": { - "text/plain": "Profile" - }, - "metadata": {} - } - ], - "execution_count": 11 + "outputs": [], + "execution_count": null }, { "id": "68a56b32-9f99-43d7-aaee-0c1cd9522681", @@ -178,47 +169,27 @@ "trusted": true }, "outputs": [], - "execution_count": 12 + "execution_count": null }, { "id": "8f2a621d-b533-4ddd-8bcd-c22db2f922ec", "cell_type": "code", - "source": "wg = load_workflow_json(file_name=workflow_json_filename)\nwg", + "source": "graph = load_workflow_json(file_name=workflow_json_filename)", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 13, - "output_type": "execute_result", - "data": { - "text/plain": "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'WorkGra…", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 1, - "model_id": "f52e57854d6e4d07b6a2c11a75873efe" - } - }, - "metadata": {} - } - ], - "execution_count": 13 + "outputs": [], + "execution_count": null }, { "id": "cf80267d-c2b0-4236-bf1d-a57596985fc1", "cell_type": "code", - "source": "\nwg.run()", + "source": "with open(workflow_json_filename) as _f:\n inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\noutputs, _ = run_get_node(graph, **inputs)\n{k: v.value for k, v in outputs.items()}", "metadata": { "trusted": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": "05/24/2025 05:39:21 AM <524> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [19|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_prod_and_div3\n05/24/2025 05:39:21 AM <524> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [19|WorkGraphEngine|update_task_state]: Task: get_prod_and_div3, type: PyFunction, finished.\n05/24/2025 05:39:21 AM <524> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [19|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_sum2\n05/24/2025 05:39:21 AM <524> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [19|WorkGraphEngine|update_task_state]: Task: get_sum2, type: PyFunction, finished.\n05/24/2025 05:39:21 AM <524> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [19|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_square1\n05/24/2025 05:39:22 AM <524> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [19|WorkGraphEngine|update_task_state]: Task: get_square1, type: PyFunction, finished.\n05/24/2025 05:39:22 AM <524> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [19|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n05/24/2025 05:39:22 AM <524> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [19|WorkGraphEngine|finalize]: Finalize workgraph.\n" - } - ], - "execution_count": 14 + "outputs": [], + "execution_count": null }, { "id": "0c3503e1-0a32-40e1-845d-3fd9ec3c4c19", @@ -283,7 +254,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "## Load Workflow with pyiron_workflow\n\nFinally, the workflow is loaded into a `pyiron_workflow` `Workflow` object, drawn with `.draw()` and executed with `.run()`.", + "source": "## Load Workflow with pyiron_workflow\n\nFinally, the workflow is loaded into a `pyiron_workflow` `Workflow` object, drawn with `.draw()` and executed with `run_get_node`.", "id": "406fd07dd4bd8006" }, { diff --git a/example_workflows/arithmetic/pyiron_workflow.ipynb b/example_workflows/arithmetic/pyiron_workflow.ipynb index 491322e..9a10c92 100644 --- a/example_workflows/arithmetic/pyiron_workflow.ipynb +++ b/example_workflows/arithmetic/pyiron_workflow.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "id": "9db99131-e5ef-49a9-93f6-13c44782c119", "metadata": {}, - "source": "# pyiron_workflow\n\nThis notebook defines the arithmetic workflow with [`pyiron_workflow`](https://github.com/pyiron/pyiron_workflow) and then loads the resulting `workflow.json` into `aiida-workgraph`, `jobflow` and `pyiron_base`." + "source": "# pyiron_workflow\n\nThis notebook defines the arithmetic workflow with [`pyiron_workflow`](https://github.com/pyiron/pyiron_workflow) and then loads the resulting `workflow.json` into `aiida-core`, `jobflow` and `pyiron_base`." }, { "cell_type": "markdown", @@ -762,7 +762,7 @@ "cell_type": "markdown", "id": "1681d449-0266-4da4-8d87-40a055cf3149", "metadata": {}, - "source": "## Load Workflow with aiida\n\nThe exported `workflow.json` is loaded into `aiida-workgraph`. An AiiDA profile must be connected via `load_profile()` before the resulting `WorkGraph` can be run." + "source": "## Load Workflow with aiida\n\nThe exported `workflow.json` is loaded into `aiida-core`. An AiiDA profile must be connected via `load_profile()` before the resulting `GraphHandle` can be run." }, { "cell_type": "code", @@ -771,8 +771,12 @@ "metadata": {}, "outputs": [], "source": [ + "import json\n", + "\n", "from aiida import load_profile\n", "\n", + "from aiida.engine.launch import run_get_node\n", + "\n", "load_profile()" ] }, @@ -793,8 +797,7 @@ "metadata": {}, "outputs": [], "source": [ - "wg = load_workflow_json(file_name=workflow_json_filename)\n", - "wg" + "graph = load_workflow_json(file_name=workflow_json_filename)" ] }, { @@ -804,7 +807,10 @@ "metadata": {}, "outputs": [], "source": [ - "wg.run()" + "with open(workflow_json_filename) as _f:\n", + " inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { diff --git a/example_workflows/arithmetic/universal_workflow.ipynb b/example_workflows/arithmetic/universal_workflow.ipynb index a100ed2..c5271c8 100644 --- a/example_workflows/arithmetic/universal_workflow.ipynb +++ b/example_workflows/arithmetic/universal_workflow.ipynb @@ -60,73 +60,35 @@ }, { "cell_type": "markdown", - "source": "## Aiida\n\nLoading the JSON into `aiida-workgraph` requires connecting to an AiiDA profile first via `load_profile()`; the workflow is then reconstructed as a `WorkGraph` and executed with `.run()`.", + "source": "## Aiida\n\nLoading the JSON into `aiida-core` requires connecting to an AiiDA profile first via `load_profile()`; the workflow is then reconstructed as a `GraphHandle` and executed with `run_get_node`.", "metadata": {} }, { "cell_type": "code", - "source": "from python_workflow_definition.aiida import load_workflow_json\n\nfrom aiida import load_profile\nload_profile()", - "metadata": { - "trusted": true - }, - "outputs": [ - { - "execution_count": 3, - "output_type": "execute_result", - "data": { - "text/plain": "Profile" - }, - "metadata": {} - } - ], - "execution_count": 3 - }, - { - "cell_type": "code", - "source": "workgraph = load_workflow_json(file_name='workflow.json')", + "source": "import json\n\nfrom python_workflow_definition.aiida import load_workflow_json\n\nfrom aiida import load_profile\nfrom aiida.engine.launch import run_get_node\n\nload_profile()", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 4 + "execution_count": null }, { "cell_type": "code", - "source": "# TODO: Create inputs rather than tasks out of data nodes\nworkgraph", + "source": "graph = load_workflow_json(file_name='workflow.json')", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 5, - "output_type": "execute_result", - "data": { - "text/plain": "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'WorkGra…", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 1, - "model_id": "fc63f59e75a74fbcab9e44a06ecb6d18" - } - }, - "metadata": {} - } - ], - "execution_count": 5 + "outputs": [], + "execution_count": null }, { "cell_type": "code", - "source": "\nworkgraph.run()", + "source": "with open(\"workflow.json\") as _f:\n inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\noutputs, _ = run_get_node(graph, **inputs)\noutputs[\"result\"]", "metadata": { "trusted": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": "05/24/2025 05:39:46 AM <644> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [35|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_prod_and_div1\n05/24/2025 05:39:46 AM <644> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [35|WorkGraphEngine|update_task_state]: Task: get_prod_and_div1, type: PyFunction, finished.\n05/24/2025 05:39:46 AM <644> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [35|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_sum2\n05/24/2025 05:39:47 AM <644> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [35|WorkGraphEngine|update_task_state]: Task: get_sum2, type: PyFunction, finished.\n05/24/2025 05:39:47 AM <644> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [35|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_square3\n05/24/2025 05:39:47 AM <644> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [35|WorkGraphEngine|update_task_state]: Task: get_square3, type: PyFunction, finished.\n05/24/2025 05:39:47 AM <644> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [35|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n05/24/2025 05:39:47 AM <644> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [35|WorkGraphEngine|finalize]: Finalize workgraph.\n" - } - ], - "execution_count": 6 + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", @@ -291,7 +253,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "## pyiron_workflow\n\nFinally, the workflow is loaded into a `pyiron_workflow` `Workflow` object, drawn with `.draw()` and executed with `.run()`." + "source": "## pyiron_workflow\n\nFinally, the workflow is loaded into a `pyiron_workflow` `Workflow` object, drawn with `.draw()` and executed with `run_get_node`." }, { "metadata": {}, diff --git a/example_workflows/nfdi/aiida.ipynb b/example_workflows/nfdi/aiida.ipynb index ef72f1b..9365000 100644 --- a/example_workflows/nfdi/aiida.ipynb +++ b/example_workflows/nfdi/aiida.ipynb @@ -24,13 +24,13 @@ { "id": "106ded66-d202-46ac-82b0-2755ca309bdd", "cell_type": "markdown", - "source": "# Aiida\n\nhttps://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n\nThis notebook defines the NFDI4Ing file-based workflow benchmark with [`aiida-workgraph`](https://github.com/aiidateam/aiida-workgraph) and then loads the resulting `workflow.json` into `jobflow`, `pyiron_base` and `pyiron_workflow` to show that the same JSON file can be executed by different engines. Every stage of the workflow calls an external command-line tool (`gmsh`, `meshio`, a FEniCS-based Poisson solver, ParaView's `pvbatch`, `tectonic`) in its own conda environment and passes file paths between stages rather than in-memory Python objects — see [`workflow.py`](workflow.py) for the six pipeline functions.", + "source": "# Aiida\n\nhttps://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n\nThis notebook defines the NFDI4Ing file-based workflow benchmark with [`aiida-core`](https://github.com/aiidateam/aiida-core) and then loads the resulting `workflow.json` into `jobflow`, `pyiron_base` and `pyiron_workflow` to show that the same JSON file can be executed by different engines. Every stage of the workflow calls an external command-line tool (`gmsh`, `meshio`, a FEniCS-based Poisson solver, ParaView's `pvbatch`, `tectonic`) in its own conda environment and passes file paths between stages rather than in-memory Python objects — see [`workflow.py`](workflow.py) for the six pipeline functions.", "metadata": {} }, { "id": "11e09b78-cb72-465f-9c8b-5b77f0aa729c", "cell_type": "markdown", - "source": "## Define workflow with aiida\n\n`aiida-workgraph` represents a workflow as a `WorkGraph` of `Task`s. `load_profile()` connects to the local AiiDA profile/database that stores provenance and results for every task. `generate_mesh`, `plot_over_line`, `substitute_macros` and `compile_paper` each return a single value and can be added to the graph unchanged, but `convert_to_xdmf` and `poisson` return a `dict`, so they are wrapped with `task(outputs=[...])` to expose each dictionary key as its own output socket that later tasks can connect to.", + "source": "## Define workflow with aiida\n\n`aiida-core` represents a workflow as a `GraphHandle` of `@task`s. `load_profile()` connects to the local AiiDA profile/database that stores provenance and results for every task. `generate_mesh`, `plot_over_line`, `substitute_macros` and `compile_paper` each return a single value and can be added to the graph unchanged, but `convert_to_xdmf` and `poisson` return a `dict`, so their wrappers declare `TypedDict` outputs to expose each dictionary key separately for downstream tasks to connect to.", "metadata": {} }, { @@ -41,203 +41,90 @@ "trusted": true }, "outputs": [], - "execution_count": 1 + "execution_count": null }, { "id": "89c02460-b543-442c-a27e-f1757dc2262e", "cell_type": "code", - "source": "from python_workflow_definition.aiida import write_workflow_json\n\nfrom aiida_workgraph import WorkGraph, task\nfrom aiida import orm, load_profile\nload_profile()", + "source": "from python_workflow_definition.aiida import write_workflow_json\n\nfrom aiida.engine import graph, task\n", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 2, - "output_type": "execute_result", - "data": { - "text/plain": "Profile" - }, - "metadata": {} - } - ], - "execution_count": 2 + "outputs": [], + "execution_count": null }, { "id": "6dac225a1ce86b25", "cell_type": "code", - "source": "from workflow import (\n generate_mesh,\n convert_to_xdmf as _convert_to_xdmf,\n poisson as _poisson,\n plot_over_line,\n substitute_macros,\n compile_paper,\n)", + "source": "import typing as t\nfrom typing import Any\n\nfrom workflow import (\n generate_mesh as _generate_mesh,\n convert_to_xdmf as _convert_to_xdmf,\n poisson as _poisson,\n plot_over_line as _plot_over_line,\n substitute_macros as _substitute_macros,\n compile_paper as _compile_paper,\n)\n\n\nclass XdmfOut(t.TypedDict):\n xdmf_file: Any\n h5_file: Any\n\n\nclass PoissonOut(t.TypedDict):\n numdofs: Any\n pvd_file: Any\n vtu_file: Any\n\n\ndef generate_mesh(domain_size, source_directory) -> Any:\n return _generate_mesh(domain_size=domain_size, source_directory=source_directory)\n\n\ngenerate_mesh.__module__ = \"workflow\"\ngenerate_mesh = task(generate_mesh)\n\n\ndef convert_to_xdmf(gmsh_output_file) -> XdmfOut:\n return _convert_to_xdmf(gmsh_output_file)\n\n\nconvert_to_xdmf.__module__ = \"workflow\"\nconvert_to_xdmf = task(convert_to_xdmf)\n\n\ndef poisson(meshio_output_xdmf, meshio_output_h5, source_directory) -> PoissonOut:\n return _poisson(meshio_output_xdmf, meshio_output_h5, source_directory)\n\n\npoisson.__module__ = \"workflow\"\npoisson = task(poisson)\n\n\ndef plot_over_line(poisson_output_pvd_file, poisson_output_vtu_file, source_directory) -> Any:\n return _plot_over_line(poisson_output_pvd_file, poisson_output_vtu_file, source_directory)\n\n\nplot_over_line.__module__ = \"workflow\"\nplot_over_line = task(plot_over_line)\n\n\ndef substitute_macros(pvbatch_output_file, ndofs, domain_size, source_directory) -> Any:\n return _substitute_macros(pvbatch_output_file, ndofs, domain_size, source_directory)\n\n\nsubstitute_macros.__module__ = \"workflow\"\nsubstitute_macros = task(substitute_macros)\n\n\ndef compile_paper(macros_tex, plot_file, source_directory) -> Any:\n return _compile_paper(macros_tex, plot_file, source_directory)\n\n\ncompile_paper.__module__ = \"workflow\"\ncompile_paper = task(compile_paper)", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 3 + "execution_count": null }, { "id": "c47205d289499756", "cell_type": "code", - "source": "convert_to_xdmf = task(outputs=[\"xdmf_file\", \"h5_file\"])(_convert_to_xdmf)\npoisson = task(outputs=[\"numdofs\", \"pvd_file\", \"vtu_file\"])(_poisson)", + "source": "source_directory = os.path.abspath(os.path.join(os.curdir, \"source\"))", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 4 - }, - { - "cell_type": "markdown", - "id": "e16e7b61", - "source": "AiiDA tracks every input as a typed, storable node, so the shared `domain_size` and `source_directory` values are wrapped as `orm.Float` and `orm.Str` before being connected to any task.", - "metadata": {} + "execution_count": null }, { "id": "37c9d988-1755-446c-9f7b-c32f99e280d4", "cell_type": "code", - "source": "source_directory = orm.Str(os.path.abspath(os.path.join(os.curdir, \"source\")))", - "metadata": { - "trusted": true - }, - "outputs": [], - "execution_count": 5 - }, - { - "id": "8d911f98-3b80-457f-a0f4-3cb37ebf1691", - "cell_type": "code", - "source": "domain_size = orm.Float(2.0)", + "source": "domain_size = 2.0", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 6 + "execution_count": null }, { - "id": "1e7e14ba-c4a7-485f-9bd8-44625830cca0", - "cell_type": "code", - "source": "wg = WorkGraph(\"wg-nfdi\")", - "metadata": { - "trusted": true - }, - "outputs": [], - "execution_count": 7 + "cell_type": "markdown", + "id": "e16e7b61", + "source": "The shared `domain_size` and `source_directory` values are passed as `@graph` arguments below; `write_workflow_json` records them as PWD `input` nodes.", + "metadata": {} }, { "cell_type": "markdown", "id": "2e764162", - "source": "Each `wg.add_task` call below adds one stage of the pipeline: mesh generation, XDMF conversion, the Poisson solve, line-plot postprocessing, LaTeX macro substitution and paper compilation. Outputs are wired to the next task's inputs with `.outputs.`; since the underlying functions communicate through files on disk, these connections are really file paths handed from one task to the next, not shared Python objects.", + "source": "Each `@task` call inside the `@graph` function below adds one stage of the pipeline: mesh generation, XDMF conversion, the Poisson solve, line-plot postprocessing, LaTeX macro substitution and paper compilation. Outputs are wired to the next task's inputs with `[\"\"]`; since the underlying functions communicate through files on disk, these connections are really file paths handed from one task to the next, not shared Python objects.", "metadata": {} }, { - "id": "71d411b6-cbec-489e-99e3-ba71680bcb5b", - "cell_type": "code", - "source": "gmsh_output_file = wg.add_task(\n generate_mesh,\n domain_size=domain_size,\n source_directory=source_directory,\n)", - "metadata": { - "tags": [], - "trusted": true - }, - "outputs": [], - "execution_count": 8 - }, - { - "id": "1d0d9804-f250-48b3-a5d0-a546d520f79b", - "cell_type": "code", - "source": "meshio_output_dict = wg.add_task(\n convert_to_xdmf,\n gmsh_output_file=gmsh_output_file.outputs.result,\n)", - "metadata": { - "tags": [], - "trusted": true - }, - "outputs": [], - "execution_count": 9 - }, - { - "id": "7b69bcff-e2b1-4d4a-b62c-6a1c86eeb590", - "cell_type": "code", - "source": "poisson_dict = wg.add_task(\n poisson,\n meshio_output_xdmf=meshio_output_dict.outputs.xdmf_file, \n meshio_output_h5=meshio_output_dict.outputs.h5_file,\n source_directory=source_directory,\n)", - "metadata": { - "tags": [], - "trusted": true - }, - "outputs": [], - "execution_count": 10 - }, - { - "id": "3c4a29b0-eb1e-490a-8be0-e03cfff15e0a", - "cell_type": "code", - "source": "pvbatch_output_file = wg.add_task(\n plot_over_line,\n poisson_output_pvd_file=poisson_dict.outputs.pvd_file, \n poisson_output_vtu_file=poisson_dict.outputs.vtu_file,\n source_directory=source_directory,\n)", - "metadata": { - "tags": [], - "trusted": true - }, - "outputs": [], - "execution_count": 11 - }, - { - "id": "a0a4c233-322d-4723-9627-62ca2487bfa9", - "cell_type": "code", - "source": "macros_tex_file = wg.add_task(\n substitute_macros,\n pvbatch_output_file=pvbatch_output_file.outputs.result, \n ndofs=poisson_dict.outputs.numdofs, \n domain_size=domain_size,\n source_directory=source_directory,\n)", - "metadata": { - "tags": [], - "trusted": true - }, - "outputs": [], - "execution_count": 12 - }, - { - "id": "c281408f-e63d-4380-a7e6-c595d49fbb8f", + "id": "8d911f98-3b80-457f-a0f4-3cb37ebf1691", "cell_type": "code", - "source": "paper_output = wg.add_task(\n compile_paper,\n macros_tex=macros_tex_file.outputs.result, \n plot_file=pvbatch_output_file.outputs.result,\n source_directory=source_directory,\n)", + "source": "@graph\ndef nfdi(source_directory, domain_size=2.0):\n gmsh_output = generate_mesh(domain_size=domain_size, source_directory=source_directory)\n meshio_output = convert_to_xdmf(gmsh_output_file=gmsh_output)\n poisson_output = poisson(meshio_output_xdmf=meshio_output[\"xdmf_file\"], meshio_output_h5=meshio_output[\"h5_file\"], source_directory=source_directory)\n pvbatch_output = plot_over_line(poisson_output_pvd_file=poisson_output[\"pvd_file\"], poisson_output_vtu_file=poisson_output[\"vtu_file\"], source_directory=source_directory)\n macros_tex = substitute_macros(pvbatch_output_file=pvbatch_output, ndofs=poisson_output[\"numdofs\"], domain_size=domain_size, source_directory=source_directory)\n return {\"paper\": compile_paper(macros_tex=macros_tex, plot_file=pvbatch_output, source_directory=source_directory)}", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 13 + "execution_count": null }, { - "id": "a384d70a-8ef5-4fdd-880c-56ac7eaf87b9", - "cell_type": "code", - "source": "wg", - "metadata": { - "trusted": true - }, - "outputs": [ - { - "execution_count": 14, - "output_type": "execute_result", - "data": { - "text/plain": "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'wg-nfdi…", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 1, - "model_id": "17520c019e5c413fb7a119ecc997c3ea" - } - }, - "metadata": {} - } - ], - "execution_count": 14 - }, - { - "cell_type": "markdown", - "id": "a7bd7df7", - "source": "The `wg` widget above renders the assembled task graph. Once satisfied, `write_workflow_json` serializes it into the PWD `workflow.json` format — one `function` node per task, `input` nodes for `domain_size`/`source_directory`, and edges recording which task output feeds which task's input port, as shown by the `cat` output below.", - "metadata": {} - }, - { - "id": "fb23ad9c-76fd-4c0b-b546-e305d6c49796", + "id": "1e7e14ba-c4a7-485f-9bd8-44625830cca0", "cell_type": "code", "source": "workflow_json_filename = \"aiida_nfdi.json\"", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 15 + "execution_count": null }, { - "id": "4139547a-9717-4708-b1a5-8202a0382d77", + "id": "71d411b6-cbec-489e-99e3-ba71680bcb5b", "cell_type": "code", - "source": "write_workflow_json(wg=wg, file_name=workflow_json_filename)", + "source": "write_workflow_json(\n nfdi,\n file_name=workflow_json_filename,\n inputs={\"source_directory\": source_directory},\n)", "metadata": { + "tags": [], "trusted": true }, "outputs": [], - "execution_count": 16 + "execution_count": null }, { "id": "490df6a3-e402-4620-acf2-dbcfa0c5d537", @@ -258,7 +145,7 @@ { "id": "11a829e2-face-469f-b343-2c95763b1f13", "cell_type": "markdown", - "source": "## Load Workflow with jobflow\n\n`python_workflow_definition.jobflow.load_workflow_json` reconstructs the same six-stage pipeline as a jobflow `Flow`, purely from `aiida_nfdi.json` — no reference to the `aiida-workgraph` objects defined above. `run_locally` then executes the jobs in dependency order, re-running `gmsh`, `meshio`, the Poisson solver, `pvbatch` and `tectonic` exactly as before.", + "source": "## Load Workflow with jobflow\n\n`python_workflow_definition.jobflow.load_workflow_json` reconstructs the same six-stage pipeline as a jobflow `Flow`, purely from `aiida_nfdi.json` — no reference to the `aiida-core` objects defined above. `run_locally` then executes the jobs in dependency order, re-running `gmsh`, `meshio`, the Poisson solver, `pvbatch` and `tectonic` exactly as before.", "metadata": {} }, { diff --git a/example_workflows/nfdi/executorlib.ipynb b/example_workflows/nfdi/executorlib.ipynb index 8a2e0ba..2f3ed0e 100644 --- a/example_workflows/nfdi/executorlib.ipynb +++ b/example_workflows/nfdi/executorlib.ipynb @@ -301,34 +301,27 @@ "cell_type": "markdown", "id": "d789971e-8f41-45fa-832a-11fd72dea96e", "metadata": {}, - "source": "## Load Workflow with aiida\n\nThe JSON produced by `executorlib` is loaded into an `aiida-workgraph` `WorkGraph` via `load_workflow_json`, after connecting to a local AiiDA profile with `load_profile()`. `wg.run()` then re-executes the whole pipeline under `aiida`." + "source": "## Load Workflow with aiida\n\nThe JSON produced by `executorlib` is loaded into an `aiida-core` `GraphHandle` via `load_workflow_json`, after connecting to a local AiiDA profile with `load_profile()`. `run_get_node` then re-executes the whole pipeline under `aiida`." }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "a6e85e89-5d7a-40eb-809c-ac44974e3fd7", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Profile" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ + "import json\n", + "\n", "from aiida import load_profile\n", "\n", + "from aiida.engine.launch import run_get_node\n", + "\n", "load_profile()" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "3de84fb7-b01b-4541-868a-92e881eb6e77", "metadata": {}, "outputs": [], @@ -338,72 +331,25 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "b33f5528-10cd-47c8-8723-622902978859", "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d1895b0b32c4451c8847cc76ded8f82e", - "version_major": 2, - "version_minor": 1 - }, - "text/plain": [ - "" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "wg = load_workflow_json(file_name=workflow_json_filename)\n", - "wg" + "graph = load_workflow_json(file_name=workflow_json_filename)" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "15282ca1-d339-40e7-ad68-8a7613ed08da", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "01/16/2026 08:09:53 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|continue_workgraph]: tasks ready to run: generate_mesh\n", - "01/16/2026 08:09:56 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|update_task_state]: Task: generate_mesh, type: PYFUNCTION, finished.\n", - "01/16/2026 08:09:56 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|continue_workgraph]: tasks ready to run: convert_to_xdmf\n", - "01/16/2026 08:10:00 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|update_task_state]: Task: convert_to_xdmf, type: PYFUNCTION, finished.\n", - "01/16/2026 08:10:00 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|continue_workgraph]: tasks ready to run: poisson\n", - "/home/jan/miniforge3/envs/processing/lib/python3.9/site-packages/ufl/__init__.py:250: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.\n", - " import pkg_resources\n", - "01/16/2026 08:10:04 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|update_task_state]: Task: poisson, type: PYFUNCTION, finished.\n", - "01/16/2026 08:10:05 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|continue_workgraph]: tasks ready to run: plot_over_line\n", - "01/16/2026 08:10:09 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|update_task_state]: Task: plot_over_line, type: PYFUNCTION, finished.\n", - "01/16/2026 08:10:09 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|continue_workgraph]: tasks ready to run: substitute_macros\n", - "01/16/2026 08:10:11 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|update_task_state]: Task: substitute_macros, type: PYFUNCTION, finished.\n", - "01/16/2026 08:10:11 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|continue_workgraph]: tasks ready to run: compile_paper\n", - "01/16/2026 08:10:16 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|update_task_state]: Task: compile_paper, type: PYFUNCTION, finished.\n", - "01/16/2026 08:10:16 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n", - "01/16/2026 08:10:16 AM <39943> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1584|WorkGraphEngine|finalize]: Finalize workgraph.\n" - ] - }, - { - "data": { - "text/plain": [ - "{}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "wg.run()" + "with open(workflow_json_filename) as _f:\n", + " inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { diff --git a/example_workflows/nfdi/jobflow.ipynb b/example_workflows/nfdi/jobflow.ipynb index 77c5041..4819fd7 100644 --- a/example_workflows/nfdi/jobflow.ipynb +++ b/example_workflows/nfdi/jobflow.ipynb @@ -209,27 +209,18 @@ { "id": "11a829e2-face-469f-b343-2c95763b1f13", "cell_type": "markdown", - "source": "## Load Workflow with aiida\n\n`load_workflow_json` rebuilds the pipeline as an `aiida-workgraph` `WorkGraph` from `jobflow_nfdi.json`, after connecting to a local AiiDA profile with `load_profile()`. `wg.run()` re-executes the whole pipeline under `aiida`.", + "source": "## Load Workflow with aiida\n\n`load_workflow_json` rebuilds the pipeline as an `aiida-core` `GraphHandle` from `jobflow_nfdi.json`, after connecting to a local AiiDA profile with `load_profile()`. `run_get_node` re-executes the whole pipeline under `aiida`.", "metadata": {} }, { "id": "4f3acabe-55df-479a-af4d-a23a80c4660d", "cell_type": "code", - "source": "from aiida import load_profile\n\nload_profile()", + "source": "import json\n\nfrom aiida import load_profile\n\nfrom aiida.engine.launch import run_get_node\n\nload_profile()", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 16, - "output_type": "execute_result", - "data": { - "text/plain": "Profile" - }, - "metadata": {} - } - ], - "execution_count": 16 + "outputs": [], + "execution_count": null }, { "id": "b6ba9444-a2c8-451b-b74c-19b0f69ba369", @@ -239,47 +230,27 @@ "trusted": true }, "outputs": [], - "execution_count": 17 + "execution_count": null }, { "id": "c3528d55-6bf7-47ed-a110-65c47446ba92", "cell_type": "code", - "source": "wg = load_workflow_json(file_name=workflow_json_filename)\nwg", + "source": "graph = load_workflow_json(file_name=workflow_json_filename)", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 18, - "output_type": "execute_result", - "data": { - "text/plain": "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'WorkGra…", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 1, - "model_id": "47a0a80d777f424aa98a551c4cbe7e1e" - } - }, - "metadata": {} - } - ], - "execution_count": 18 + "outputs": [], + "execution_count": null }, { "id": "5ef48778-4209-425f-8c6b-85a4cd2f92ec", "cell_type": "code", - "source": "wg.run()", + "source": "with open(workflow_json_filename) as _f:\n inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\noutputs, _ = run_get_node(graph, **inputs)\n{k: v.value for k, v in outputs.items()}", "metadata": { "trusted": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": "05/24/2025 05:44:19 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|continue_workgraph]: tasks ready to run: generate_mesh1\n05/24/2025 05:44:21 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|update_task_state]: Task: generate_mesh1, type: PyFunction, finished.\n05/24/2025 05:44:21 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|continue_workgraph]: tasks ready to run: convert_to_xdmf2\n05/24/2025 05:44:22 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|update_task_state]: Task: convert_to_xdmf2, type: PyFunction, finished.\n05/24/2025 05:44:22 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|continue_workgraph]: tasks ready to run: poisson3\n05/24/2025 05:44:25 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|update_task_state]: Task: poisson3, type: PyFunction, finished.\n05/24/2025 05:44:25 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|continue_workgraph]: tasks ready to run: plot_over_line4\n05/24/2025 05:44:27 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|update_task_state]: Task: plot_over_line4, type: PyFunction, finished.\n05/24/2025 05:44:27 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|continue_workgraph]: tasks ready to run: substitute_macros5\n05/24/2025 05:44:29 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|update_task_state]: Task: substitute_macros5, type: PyFunction, finished.\n05/24/2025 05:44:29 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|continue_workgraph]: tasks ready to run: compile_paper6\n05/24/2025 05:44:31 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|update_task_state]: Task: compile_paper6, type: PyFunction, finished.\n05/24/2025 05:44:31 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n05/24/2025 05:44:31 AM <3034> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [51|WorkGraphEngine|finalize]: Finalize workgraph.\n" - } - ], - "execution_count": 19 + "outputs": [], + "execution_count": null }, { "id": "397b16a2-e1ec-4eec-8562-1c84f585c347", diff --git a/example_workflows/nfdi/pyiron_base.ipynb b/example_workflows/nfdi/pyiron_base.ipynb index 164b5af..f22f884 100644 --- a/example_workflows/nfdi/pyiron_base.ipynb +++ b/example_workflows/nfdi/pyiron_base.ipynb @@ -167,7 +167,7 @@ { "cell_type": "markdown", "id": "4d1ce825", - "source": "Because pyiron builds a delayed call graph rather than an explicit `Flow`/`WorkGraph` object, `write_workflow_json` only needs the final delayed object (`paper_output`) — it walks the graph backwards to discover every upstream job.", + "source": "Because pyiron builds a delayed call graph rather than an explicit `Flow`/`GraphHandle` object, `write_workflow_json` only needs the final delayed object (`paper_output`) — it walks the graph backwards to discover every upstream job.", "metadata": {} }, { @@ -193,27 +193,18 @@ { "id": "d789971e-8f41-45fa-832a-11fd72dea96e", "cell_type": "markdown", - "source": "## Load Workflow with aiida\n\n`load_workflow_json` rebuilds the pipeline as an `aiida-workgraph` `WorkGraph` from `pyiron_base_nfdi.json`, after connecting to a local AiiDA profile with `load_profile()`. `wg.run()` re-executes the whole pipeline under `aiida`.", + "source": "## Load Workflow with aiida\n\n`load_workflow_json` rebuilds the pipeline as an `aiida-core` `GraphHandle` from `pyiron_base_nfdi.json`, after connecting to a local AiiDA profile with `load_profile()`. `run_get_node` re-executes the whole pipeline under `aiida`.", "metadata": {} }, { "id": "a6e85e89-5d7a-40eb-809c-ac44974e3fd7", "cell_type": "code", - "source": "from aiida import load_profile\n\nload_profile()", + "source": "import json\n\nfrom aiida import load_profile\n\nfrom aiida.engine.launch import run_get_node\n\nload_profile()", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 15, - "output_type": "execute_result", - "data": { - "text/plain": "Profile" - }, - "metadata": {} - } - ], - "execution_count": 15 + "outputs": [], + "execution_count": null }, { "id": "3de84fb7-b01b-4541-868a-92e881eb6e77", @@ -223,47 +214,27 @@ "trusted": true }, "outputs": [], - "execution_count": 16 + "execution_count": null }, { "id": "b33f5528-10cd-47c8-8723-622902978859", "cell_type": "code", - "source": "wg = load_workflow_json(file_name=workflow_json_filename)\nwg", + "source": "graph = load_workflow_json(file_name=workflow_json_filename)", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 17, - "output_type": "execute_result", - "data": { - "text/plain": "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'WorkGra…", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 1, - "model_id": "ecd2de75aecb4dc7b8db86590fff92bb" - } - }, - "metadata": {} - } - ], - "execution_count": 17 + "outputs": [], + "execution_count": null }, { "id": "15282ca1-d339-40e7-ad68-8a7613ed08da", "cell_type": "code", - "source": "wg.run()", + "source": "with open(workflow_json_filename) as _f:\n inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\noutputs, _ = run_get_node(graph, **inputs)\n{k: v.value for k, v in outputs.items()}", "metadata": { "trusted": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": "05/24/2025 05:45:10 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|continue_workgraph]: tasks ready to run: generate_mesh6\n05/24/2025 05:45:12 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|update_task_state]: Task: generate_mesh6, type: PyFunction, finished.\n05/24/2025 05:45:12 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|continue_workgraph]: tasks ready to run: convert_to_xdmf5\n05/24/2025 05:45:13 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|update_task_state]: Task: convert_to_xdmf5, type: PyFunction, finished.\n05/24/2025 05:45:13 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|continue_workgraph]: tasks ready to run: poisson4\n05/24/2025 05:45:16 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|update_task_state]: Task: poisson4, type: PyFunction, finished.\n05/24/2025 05:45:16 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|continue_workgraph]: tasks ready to run: plot_over_line3\n05/24/2025 05:45:18 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|update_task_state]: Task: plot_over_line3, type: PyFunction, finished.\n05/24/2025 05:45:18 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|continue_workgraph]: tasks ready to run: substitute_macros2\n05/24/2025 05:45:20 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|update_task_state]: Task: substitute_macros2, type: PyFunction, finished.\n05/24/2025 05:45:20 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|continue_workgraph]: tasks ready to run: compile_paper1\n05/24/2025 05:45:22 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|update_task_state]: Task: compile_paper1, type: PyFunction, finished.\n05/24/2025 05:45:22 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n05/24/2025 05:45:22 AM <4326> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [81|WorkGraphEngine|finalize]: Finalize workgraph.\n" - } - ], - "execution_count": 18 + "outputs": [], + "execution_count": null }, { "id": "55dc8d12-dfe6-4465-a368-b7e590ae6800", diff --git a/example_workflows/nfdi/pyiron_workflow.ipynb b/example_workflows/nfdi/pyiron_workflow.ipynb index 2bc76e3..cac3426 100644 --- a/example_workflows/nfdi/pyiron_workflow.ipynb +++ b/example_workflows/nfdi/pyiron_workflow.ipynb @@ -223,27 +223,18 @@ { "id": "d789971e-8f41-45fa-832a-11fd72dea96e", "cell_type": "markdown", - "source": "## Load Workflow with aiida\n\n`load_workflow_json` rebuilds the pipeline as an `aiida-workgraph` `WorkGraph` from `pyiron_workflow_nfdi.json`, after connecting to a local AiiDA profile with `load_profile()`. `wg.run()` re-executes the whole pipeline under `aiida`.", + "source": "## Load Workflow with aiida\n\n`load_workflow_json` rebuilds the pipeline as an `aiida-core` `GraphHandle` from `pyiron_workflow_nfdi.json`, after connecting to a local AiiDA profile with `load_profile()`. `run_get_node` re-executes the whole pipeline under `aiida`.", "metadata": {} }, { "id": "a6e85e89-5d7a-40eb-809c-ac44974e3fd7", "cell_type": "code", - "source": "from aiida import load_profile\n\nload_profile()", + "source": "import json\n\nfrom aiida import load_profile\n\nfrom aiida.engine.launch import run_get_node\n\nload_profile()", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 17, - "output_type": "execute_result", - "data": { - "text/plain": "Profile" - }, - "metadata": {} - } - ], - "execution_count": 17 + "outputs": [], + "execution_count": null }, { "id": "3de84fb7-b01b-4541-868a-92e881eb6e77", @@ -253,47 +244,27 @@ "trusted": true }, "outputs": [], - "execution_count": 18 + "execution_count": null }, { "id": "b33f5528-10cd-47c8-8723-622902978859", "cell_type": "code", - "source": "wg = load_workflow_json(file_name=workflow_json_filename)\nwg", + "source": "graph = load_workflow_json(file_name=workflow_json_filename)", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 19, - "output_type": "execute_result", - "data": { - "text/plain": "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'WorkGra…", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 1, - "model_id": "6641ced7603742b8b28900587d764b7c" - } - }, - "metadata": {} - } - ], - "execution_count": 19 + "outputs": [], + "execution_count": null }, { "id": "15282ca1-d339-40e7-ad68-8a7613ed08da", "cell_type": "code", - "source": "wg.run()", + "source": "with open(workflow_json_filename) as _f:\n inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\noutputs, _ = run_get_node(graph, **inputs)\n{k: v.value for k, v in outputs.items()}", "metadata": { "trusted": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": "05/24/2025 09:30:18 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: generate_mesh1\n05/24/2025 09:30:20 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|update_task_state]: Task: generate_mesh1, type: PyFunction, finished.\n05/24/2025 09:30:20 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: convert_to_xdmf2\n05/24/2025 09:30:22 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|update_task_state]: Task: convert_to_xdmf2, type: PyFunction, finished.\n05/24/2025 09:30:22 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: poisson3\n05/24/2025 09:30:30 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|update_task_state]: Task: poisson3, type: PyFunction, finished.\n05/24/2025 09:30:30 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: plot_over_line4\n05/24/2025 09:30:33 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|update_task_state]: Task: plot_over_line4, type: PyFunction, finished.\n05/24/2025 09:30:33 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: substitute_macros5\n05/24/2025 09:30:34 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|update_task_state]: Task: substitute_macros5, type: PyFunction, finished.\n05/24/2025 09:30:34 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: compile_paper6\n05/24/2025 09:30:50 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|update_task_state]: Task: compile_paper6, type: PyFunction, finished.\n05/24/2025 09:30:50 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n05/24/2025 09:30:50 AM <282> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [3|WorkGraphEngine|finalize]: Finalize workgraph.\n" - } - ], - "execution_count": 20 + "outputs": [], + "execution_count": null }, { "id": "55dc8d12-dfe6-4465-a368-b7e590ae6800", diff --git a/example_workflows/nfdi/universal_workflow.ipynb b/example_workflows/nfdi/universal_workflow.ipynb index 8ceeaad..72d059d 100644 --- a/example_workflows/nfdi/universal_workflow.ipynb +++ b/example_workflows/nfdi/universal_workflow.ipynb @@ -60,73 +60,44 @@ }, { "cell_type": "markdown", - "source": "## Aiida\n\nLoading the JSON into `aiida-workgraph` requires connecting to an AiiDA profile first via `load_profile()`; the workflow is then reconstructed as a `WorkGraph` and executed with `.run()`, which re-runs `generate_mesh`, `convert_to_xdmf`, `poisson`, `plot_over_line`, `substitute_macros` and `compile_paper` in order.", + "source": "## Aiida\n\nLoading the JSON into `aiida-core` requires connecting to an AiiDA profile first via `load_profile()`; the workflow is then reconstructed as a `GraphHandle` and executed with `run_get_node`, which re-runs `generate_mesh`, `convert_to_xdmf`, `poisson`, `plot_over_line`, `substitute_macros` and `compile_paper` in order.", "metadata": {} }, { "cell_type": "code", - "source": "from python_workflow_definition.aiida import load_workflow_json\n\nfrom aiida import load_profile\nload_profile()", + "source": "import json\n\nfrom python_workflow_definition.aiida import load_workflow_json\n\nfrom aiida import load_profile\nfrom aiida.engine.launch import run_get_node\n\nload_profile()", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 3, - "output_type": "execute_result", - "data": { - "text/plain": "Profile" - }, - "metadata": {} - } - ], - "execution_count": 3 + "outputs": [], + "execution_count": null }, { "cell_type": "code", - "source": "workgraph = load_workflow_json(file_name='workflow.json')", + "source": "graph = load_workflow_json(file_name='workflow.json')", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 4 + "execution_count": null }, { "cell_type": "code", - "source": "# TODO: Create inputs rather than tasks out of data nodes\nworkgraph", + "source": "with open(\"workflow.json\") as _f:\n inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\noutputs, _ = run_get_node(graph, **inputs)\n{k: v.value for k, v in outputs.items()}", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 5, - "output_type": "execute_result", - "data": { - "text/plain": "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'WorkGra…", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 1, - "model_id": "b9f711f3af4b4f9c9f6260d595667b1d" - } - }, - "metadata": {} - } - ], - "execution_count": 5 + "outputs": [], + "execution_count": null }, { "cell_type": "code", - "source": "\nworkgraph.run()", + "source": "outputs", "metadata": { "trusted": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": "05/24/2025 05:45:59 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: generate_mesh1\n05/24/2025 05:46:01 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: generate_mesh1, type: PyFunction, finished.\n05/24/2025 05:46:01 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: convert_to_xdmf2\n05/24/2025 05:46:03 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: convert_to_xdmf2, type: PyFunction, finished.\n05/24/2025 05:46:03 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: poisson3\n05/24/2025 05:46:06 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: poisson3, type: PyFunction, finished.\n05/24/2025 05:46:06 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: plot_over_line4\n05/24/2025 05:46:08 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: plot_over_line4, type: PyFunction, finished.\n05/24/2025 05:46:08 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: substitute_macros5\n05/24/2025 05:46:09 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: substitute_macros5, type: PyFunction, finished.\n05/24/2025 05:46:09 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: compile_paper6\n05/24/2025 05:46:12 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: compile_paper6, type: PyFunction, finished.\n05/24/2025 05:46:12 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n05/24/2025 05:46:12 AM <5606> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|finalize]: Finalize workgraph.\n" - } - ], - "execution_count": 6 + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", @@ -291,7 +262,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "## Load Workflow with pyiron_workflow\n\nFinally, the workflow is loaded into a `pyiron_workflow` `Workflow` object, drawn with `.draw()` and executed with `.run()`." + "source": "## Load Workflow with pyiron_workflow\n\nFinally, the workflow is loaded into a `pyiron_workflow` `Workflow` object, drawn with `.draw()` and executed with `run_get_node`." }, { "metadata": {}, diff --git a/example_workflows/quantum_espresso/aiida.ipynb b/example_workflows/quantum_espresso/aiida.ipynb index 8b297f2..1968e86 100644 --- a/example_workflows/quantum_espresso/aiida.ipynb +++ b/example_workflows/quantum_espresso/aiida.ipynb @@ -3,197 +3,158 @@ { "cell_type": "markdown", "metadata": {}, - "source": "# aiida-workgraph\n\nThis notebook builds the Quantum Espresso energy-volume-curve workflow directly with [`aiida-workgraph`](https://github.com/aiidateam/aiida-workgraph) and exports it as a PWD `workflow.json`. The second half of the notebook loads that same JSON file with `jobflow`, `pyiron_base` and `pyiron_workflow` to show that a graph built with one engine can be executed by another." + "source": "# aiida-core\n\nThis notebook builds the Quantum Espresso energy-volume-curve workflow directly with [`aiida-core`](https://github.com/aiidateam/aiida-core) and exports it as a PWD `workflow.json`. The second half of the notebook loads that same JSON file with `jobflow`, `pyiron_base` and `pyiron_workflow` to show that a graph built with one engine can be executed by another." }, { "cell_type": "markdown", "metadata": {}, - "source": "## Define workflow with aiida\n\n`aiida-workgraph` represents a workflow as a `WorkGraph` built from `task`s, each wrapping a Python function; connecting a task's output to another task's input builds up the graph. `load_profile()` connects to the local AiiDA profile/database, which is required before any AiiDA node can be created, since every task and its result are stored there for provenance." + "source": "## Define workflow with aiida\n\n`aiida-core` represents a workflow as a `GraphHandle` built from `task`s, each wrapping a Python function; connecting a task's output to another task's input builds up the graph. `load_profile()` connects to the local AiiDA profile/database, which is required before any AiiDA node can be created, since every task and its result are stored there for provenance." }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "trusted": true }, "outputs": [], "source": [ "from python_workflow_definition.aiida import write_workflow_json\n", - "from python_workflow_definition.shared import get_dict, get_list\n", - "\n", - "from aiida import load_profile, orm\n", + "from python_workflow_definition.shared import get_dict as _shared_get_dict\n", "\n", - "load_profile()\n", + "from aiida.engine import graph, task\n", "\n", "workflow_json_filename = \"aiida_qe.json\"" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "trusted": true }, "outputs": [], "source": [ - "from aiida_workgraph import task, WorkGraph" + "from workflow import (\n", + " generate_structures as _generate_structures,\n", + " get_bulk_structure as _get_bulk_structure,\n", + " calculate_qe as _calculate_qe,\n", + " plot_energy_volume_curve as _plot_energy_volume_curve,\n", + " collect_volumes as _collect_volumes,\n", + " collect_energies as _collect_energies,\n", + ")" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "trusted": true }, "outputs": [], "source": [ + "import typing as t\n", + "from typing import Any\n", + "\n", "from workflow import (\n", - " generate_structures,\n", - " get_bulk_structure,\n", + " generate_structures as _generate_structures,\n", + " get_bulk_structure as _get_bulk_structure,\n", " calculate_qe as _calculate_qe,\n", - " plot_energy_volume_curve,\n", - ")" + " plot_energy_volume_curve as _plot_energy_volume_curve,\n", + " collect_volumes as _collect_volumes,\n", + " collect_energies as _collect_energies,\n", + ")\n", + "\n", + "\n", + "class QeOut(t.TypedDict):\n", + " energy: Any\n", + " volume: Any\n", + " structure: Any\n", + "\n", + "\n", + "class StructOut(t.TypedDict):\n", + " s_0: Any\n", + " s_1: Any\n", + " s_2: Any\n", + " s_3: Any\n", + " s_4: Any\n", + "\n", + "\n", + "def get_bulk_structure(element, a, cubic) -> Any:\n", + " return _get_bulk_structure(element=element, a=a, cubic=cubic)\n", + "\n", + "\n", + "get_bulk_structure.__module__ = \"workflow\"\n", + "get_bulk_structure = task(get_bulk_structure)\n", + "\n", + "\n", + "def get_dict(structure, calculation, kpts, pseudopotentials, smearing) -> Any:\n", + " return _shared_get_dict(structure=structure, calculation=calculation, kpts=kpts, pseudopotentials=pseudopotentials, smearing=smearing)\n", + "\n", + "\n", + "get_dict.__module__ = \"python_workflow_definition.shared\"\n", + "get_dict = task(get_dict)\n", + "\n", + "\n", + "def calculate_qe(input_dict, working_directory) -> QeOut:\n", + " return _calculate_qe(input_dict=input_dict, working_directory=working_directory)\n", + "\n", + "\n", + "calculate_qe.__module__ = \"workflow\"\n", + "calculate_qe = task(calculate_qe)\n", + "\n", + "\n", + "def generate_structures(structure, strain_lst) -> StructOut:\n", + " return _generate_structures(structure=structure, strain_lst=strain_lst)\n", + "\n", + "\n", + "generate_structures.__module__ = \"workflow\"\n", + "generate_structures = task(generate_structures)\n", + "\n", + "\n", + "def collect_volumes(v0, v1, v2, v3, v4) -> Any:\n", + " return _collect_volumes(v0, v1, v2, v3, v4)\n", + "\n", + "\n", + "collect_volumes.__module__ = \"workflow\"\n", + "collect_volumes = task(collect_volumes)\n", + "\n", + "\n", + "def collect_energies(v0, v1, v2, v3, v4) -> Any:\n", + " return _collect_energies(v0, v1, v2, v3, v4)\n", + "\n", + "\n", + "collect_energies.__module__ = \"workflow\"\n", + "collect_energies = task(collect_energies)\n", + "\n", + "\n", + "def plot_energy_volume_curve(volume_lst, energy_lst) -> Any:\n", + " return _plot_energy_volume_curve(volume_lst=volume_lst, energy_lst=energy_lst)\n", + "\n", + "\n", + "plot_energy_volume_curve.__module__ = \"workflow\"\n", + "plot_energy_volume_curve = task(plot_energy_volume_curve)" ] }, { "cell_type": "markdown", - "source": "`calculate_qe` (defined in [`workflow.py`](workflow.py)) returns a dict with `energy`, `volume` and `structure` keys. Wrapping it with `task(outputs=[...])` tells `aiida-workgraph` to expose these as separate output sockets (`.outputs.energy`, `.outputs.volume`, `.outputs.structure`) instead of a single opaque result, so downstream tasks can connect to individual keys.", + "source": "`calculate_qe` (defined in [`workflow.py`](workflow.py)) returns a dict with `energy`, `volume` and `structure` keys. Its wrapper declares a `QeOut` `TypedDict`, exposing these as separate outputs (`[\"energy\"]`, `[\"volume\"]`, `[\"structure\"]`) instead of a single opaque result, so downstream tasks can connect to individual keys.", "metadata": {} }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "calculate_qe = task(outputs=[\"energy\", \"volume\", \"structure\"])(_calculate_qe)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "wg = WorkGraph(\"wg-qe\")" - ] - }, { "cell_type": "markdown", "metadata": {}, - "source": "### Prepare the inputs\n\nScalar inputs are wrapped as AiiDA data types (`orm.Str`, `orm.Float`, `orm.Bool`, `orm.Dict`, `orm.List`) so AiiDA can track their provenance. `strain_lst` are the five volumetric strains (0.9-1.1) that will later be applied to the relaxed structure to sample the energy-volume curve." - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "element = orm.Str(\"Al\")\n", - "a = orm.Float(4.04)\n", - "cubic = orm.Bool(True)\n", - "relax_workdir = orm.Str(\"mini\")\n", - "pseudopotentials = orm.Dict({\"Al\": \"Al.pbe-n-kjpaw_psl.1.0.0.UPF\"})\n", - "kpts = orm.List([3, 3, 3])\n", - "calc_type_relax = orm.Str(\"vc-relax\")\n", - "calc_type_scf = orm.Str(\"scf\")\n", - "smearing = orm.Float(0.02)\n", - "strain_lst = orm.List([0.9, 0.95, 1.0, 1.05, 1.1])" - ] + "source": "### Prepare the inputs\n\nThe `@graph` signature below declares every input with its default value. `strain_lst` are the five volumetric strains (0.9-1.1) that will later be applied to the relaxed structure to sample the energy-volume curve." }, { "cell_type": "markdown", "metadata": {}, "source": "### Build the task graph\n\nThe pipeline is: `get_bulk_structure` builds the initial Al structure, `calculate_qe` relaxes it (`vc-relax`), `generate_structures` applies the strains, and one `calculate_qe` (`scf`) task per strained structure computes its energy and volume. `get_dict` bundles the structure, calculation type, k-points, pseudopotentials and smearing into the `input_dict` argument `calculate_qe` expects." }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "get_bulk_structure_task = wg.add_task(\n", - " get_bulk_structure,\n", - " element=element,\n", - " a=a,\n", - " cubic=cubic,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "relax_prepare_input_dict_task = wg.add_task(\n", - " get_dict,\n", - " structure=get_bulk_structure_task.outputs.result,\n", - " calculation=calc_type_relax,\n", - " kpts=kpts,\n", - " pseudopotentials=pseudopotentials,\n", - " smearing=smearing,\n", - ")\n", - "\n", - "relax_task = wg.add_task(\n", - " calculate_qe,\n", - " input_dict=relax_prepare_input_dict_task.outputs.result,\n", - " working_directory=relax_workdir,\n", - ")" - ] - }, { "cell_type": "markdown", - "source": "`generate_structures` applies each of the five strains to the relaxed structure, producing five strained structures (`s_0` ... `s_4`). This is the fan-out point of the workflow: the loop below adds one independent `scf` `calculate_qe` task per strained structure, and their `energy`/`volume` outputs are collected into two lists (via `get_list` tasks) for the final plot.", + "source": "`generate_structures` applies each of the five strains to the relaxed structure, producing five strained structures (`s_0` ... `s_4`). This is the fan-out point of the workflow: one independent `scf` `calculate_qe` task per strained structure is wired explicitly in the graph below, and their `energy`/`volume` outputs are collected into two lists (via `collect_volumes` and `collect_energies`) for the final plot.", "metadata": {} }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "generate_structures_task = wg.add_task(\n", - " generate_structures,\n", - " structure=relax_task.outputs.structure,\n", - " strain_lst=strain_lst,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "get_volumes_task = wg.add_task(get_list)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "get_energies_task = wg.add_task(get_list)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -202,90 +163,40 @@ }, "outputs": [], "source": [ - "strain_dir_tasks, scf_qe_tasks, scf_get_dict_tasks = [], [], []\n", - "\n", - "for i, strain in enumerate(strain_lst):\n", - "\n", - " structure_key = f\"s_{i}\"\n", - " strain_dir = orm.Str(f\"strain_{i}\")\n", - " generate_structures_task.add_output(\"workgraph.any\", structure_key)\n", - "\n", - " scf_prepare_input_dict_task = wg.add_task(\n", - " get_dict,\n", - " structure=generate_structures_task.outputs[structure_key],\n", - " calculation=calc_type_scf,\n", - " kpts=kpts,\n", - " pseudopotentials=pseudopotentials,\n", - " smearing=smearing,\n", - " )\n", - "\n", - " scf_qe_task = wg.add_task(\n", - " calculate_qe,\n", - " input_dict=scf_prepare_input_dict_task.outputs.result,\n", - " working_directory=strain_dir,\n", - " )\n", - "\n", - " # collect energy and volume\n", - " get_energies_task.set_inputs({f\"{i}\": scf_qe_task.outputs.energy})\n", - " get_volumes_task.set_inputs({f\"{i}\": scf_qe_task.outputs.volume})" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "plot_energy_volume_curve_task = wg.add_task(\n", - " plot_energy_volume_curve,\n", - " volume_lst=get_volumes_task.outputs.result,\n", - " energy_lst=get_energies_task.outputs.result,\n", - ")" + "@graph\n", + "def qe_eos(element=\"Al\", a=4.04, cubic=True, relax_workdir=\"mini\",\n", + " pseudopotentials={\"Al\": \"Al.pbe-n-kjpaw_psl.1.0.0.UPF\"},\n", + " kpts=[3, 3, 3], calc_type_relax=\"vc-relax\",\n", + " calc_type_scf=\"scf\", smearing=0.02,\n", + " strain_lst=[0.9, 0.95, 1.0, 1.05, 1.1]):\n", + " bulk = get_bulk_structure(element=element, a=a, cubic=cubic)\n", + " relax_inputs = get_dict(structure=bulk, calculation=calc_type_relax, kpts=kpts, pseudopotentials=pseudopotentials, smearing=smearing)\n", + " relaxed = calculate_qe(input_dict=relax_inputs, working_directory=relax_workdir)\n", + " strained = generate_structures(structure=relaxed[\"structure\"], strain_lst=strain_lst)\n", + " scf_inputs_0 = get_dict(structure=strained[\"s_0\"], calculation=calc_type_scf, kpts=kpts, pseudopotentials=pseudopotentials, smearing=smearing)\n", + " scf_0 = calculate_qe(input_dict=scf_inputs_0, working_directory=\"strain_0\")\n", + " scf_inputs_1 = get_dict(structure=strained[\"s_1\"], calculation=calc_type_scf, kpts=kpts, pseudopotentials=pseudopotentials, smearing=smearing)\n", + " scf_1 = calculate_qe(input_dict=scf_inputs_1, working_directory=\"strain_1\")\n", + " scf_inputs_2 = get_dict(structure=strained[\"s_2\"], calculation=calc_type_scf, kpts=kpts, pseudopotentials=pseudopotentials, smearing=smearing)\n", + " scf_2 = calculate_qe(input_dict=scf_inputs_2, working_directory=\"strain_2\")\n", + " scf_inputs_3 = get_dict(structure=strained[\"s_3\"], calculation=calc_type_scf, kpts=kpts, pseudopotentials=pseudopotentials, smearing=smearing)\n", + " scf_3 = calculate_qe(input_dict=scf_inputs_3, working_directory=\"strain_3\")\n", + " scf_inputs_4 = get_dict(structure=strained[\"s_4\"], calculation=calc_type_scf, kpts=kpts, pseudopotentials=pseudopotentials, smearing=smearing)\n", + " scf_4 = calculate_qe(input_dict=scf_inputs_4, working_directory=\"strain_4\")\n", + " volumes = collect_volumes(v0=scf_0[\"volume\"], v1=scf_1[\"volume\"], v2=scf_2[\"volume\"], v3=scf_3[\"volume\"], v4=scf_4[\"volume\"])\n", + " energies = collect_energies(v0=scf_0[\"energy\"], v1=scf_1[\"energy\"], v2=scf_2[\"energy\"], v3=scf_3[\"energy\"], v4=scf_4[\"energy\"])\n", + " return {\"result\": plot_energy_volume_curve(volume_lst=volumes, energy_lst=energies)}\n" ] }, - { - "cell_type": "markdown", - "source": "Displaying `wg` renders an interactive `NodeGraphWidget` of the task graph, which is useful to check the wiring before running it. `write_workflow_json` then serializes the `WorkGraph` into the PWD `workflow.json` format (nodes = functions/inputs/outputs, edges = data connections) so it can be loaded by another engine.", - "metadata": {} - }, { "cell_type": "code", - "execution_count": 14, - "metadata": { - "trusted": true - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "8cde6dcdf8224a4a9c002550a0165c73", - "version_major": 2, - "version_minor": 1 - }, - "text/plain": [ - "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'wg-qe',…" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "wg" - ] - }, - { - "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": { "trusted": true }, "outputs": [], "source": [ - "_ = write_workflow_json(wg=wg, file_name=workflow_json_filename)\n" + "write_workflow_json(qe_eos, file_name=workflow_json_filename)" ] }, { diff --git a/example_workflows/quantum_espresso/executorlib.ipynb b/example_workflows/quantum_espresso/executorlib.ipynb index e4ebd6f..673d2c9 100644 --- a/example_workflows/quantum_espresso/executorlib.ipynb +++ b/example_workflows/quantum_espresso/executorlib.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "id": "be2d61b0-0d47-4349-b4b0-1b767c961644", "metadata": {}, - "source": "# executorlib\n\nThis notebook builds the Quantum Espresso energy-volume-curve workflow with [`executorlib`](https://github.com/pyiron/executorlib), a lightweight `concurrent.futures`-style executor. Tasks are submitted individually with `.submit()`, and the resulting graph is exported as a PWD `workflow.json` as a side effect. The second half of the notebook loads that same JSON with `aiida-workgraph`, `jobflow` and `pyiron_base`/`pyiron_workflow`." + "source": "# executorlib\n\nThis notebook builds the Quantum Espresso energy-volume-curve workflow with [`executorlib`](https://github.com/pyiron/executorlib), a lightweight `concurrent.futures`-style executor. Tasks are submitted individually with `.submit()`, and the resulting graph is exported as a PWD `workflow.json` as a side effect. The second half of the notebook loads that same JSON with `aiida-core`, `jobflow` and `pyiron_base`/`pyiron_workflow`." }, { "cell_type": "markdown", @@ -690,34 +690,27 @@ "cell_type": "markdown", "id": "7d75a2f6-6fad-49c8-bd29-37cca1b84441", "metadata": {}, - "source": "## Load Workflow with aiida\n\n`load_workflow_json` reconstructs the graph as an `aiida-workgraph` `WorkGraph`. To demonstrate that the loaded graph can still be edited like one built natively with `aiida-workgraph`, the lattice constant `a` is overridden (4.04 -> 4.05) before running it with `wg.run()`." + "source": "## Load Workflow with aiida\n\n`load_workflow_json` reconstructs the graph as an `aiida-core` `GraphHandle`. To demonstrate that the loaded graph can still be edited like one built natively with `aiida-core`, the lattice constant `a` is overridden (4.04 -> 4.05) before running it with `run_get_node`." }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "28760464-2a2c-40c2-9451-20713da2ba2c", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Profile" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "from aiida import orm, load_profile\n", + "import json\n", + "\n", + "from aiida import load_profile\n", + "\n", + "from aiida.engine.launch import run_get_node\n", "\n", "load_profile()" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "a442fe46-057c-469b-a5e0-173ed4829dfb", "metadata": {}, "outputs": [], @@ -727,12 +720,12 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "32fcd4b2-4f0a-442d-b098-827672823796", "metadata": {}, "outputs": [], "source": [ - "wg = load_workflow_json(file_name=workflow_json_filename)" + "graph = load_workflow_json(file_name=workflow_json_filename)" ] }, { @@ -742,86 +735,11 @@ "metadata": {}, "outputs": [], "source": [ - "wg.tasks.get_bulk_structure.inputs.a.value = orm.Float(4.05)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a80b59bd-fe30-49c6-92ca-35ef2d77a6fb", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "01/16/2026 08:27:06 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_bulk_structure\n", - "01/16/2026 08:27:06 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: get_bulk_structure, type: PYFUNCTION, finished.\n", - "01/16/2026 08:27:06 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict\n", - "01/16/2026 08:27:07 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: get_dict, type: PYFUNCTION, finished.\n", - "01/16/2026 08:27:07 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe\n", - "01/16/2026 08:28:16 AM <71653> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: ['energy', 'volume']\n", - "01/16/2026 08:28:17 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: calculate_qe, type: PYFUNCTION, finished.\n", - "01/16/2026 08:28:17 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: generate_structures\n", - "01/16/2026 08:28:18 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: generate_structures, type: PYFUNCTION, finished.\n", - "01/16/2026 08:28:18 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict1,get_dict2,get_dict3,get_dict4,get_dict5\n", - "01/16/2026 08:28:18 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: get_dict1, type: PYFUNCTION, finished.\n", - "01/16/2026 08:28:18 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe1,get_dict2,get_dict3,get_dict4,get_dict5\n", - "01/16/2026 08:28:34 AM <71653> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: ['structure']\n", - "01/16/2026 08:28:34 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: calculate_qe1, type: PYFUNCTION, finished.\n", - "01/16/2026 08:28:34 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict2,get_dict3,get_dict4,get_dict5\n", - "01/16/2026 08:28:35 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: get_dict2, type: PYFUNCTION, finished.\n", - "01/16/2026 08:28:35 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe2,get_dict3,get_dict4,get_dict5\n", - "01/16/2026 08:28:51 AM <71653> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: ['structure']\n", - "01/16/2026 08:28:51 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: calculate_qe2, type: PYFUNCTION, finished.\n", - "01/16/2026 08:28:51 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict3,get_dict4,get_dict5\n", - "01/16/2026 08:28:51 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: get_dict3, type: PYFUNCTION, finished.\n", - "01/16/2026 08:28:52 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe3,get_dict4,get_dict5\n", - "01/16/2026 08:29:11 AM <71653> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: ['structure']\n", - "01/16/2026 08:29:12 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: calculate_qe3, type: PYFUNCTION, finished.\n", - "01/16/2026 08:29:12 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict4,get_dict5\n", - "01/16/2026 08:29:12 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: get_dict4, type: PYFUNCTION, finished.\n", - "01/16/2026 08:29:13 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe4,get_dict5\n", - "01/16/2026 08:29:34 AM <71653> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: ['structure']\n", - "01/16/2026 08:29:35 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: calculate_qe4, type: PYFUNCTION, finished.\n", - "01/16/2026 08:29:35 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict5\n", - "01/16/2026 08:29:36 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: get_dict5, type: PYFUNCTION, finished.\n", - "01/16/2026 08:29:36 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe5\n", - "01/16/2026 08:29:58 AM <71653> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: ['structure']\n", - "01/16/2026 08:29:59 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: calculate_qe5, type: PYFUNCTION, finished.\n", - "01/16/2026 08:29:59 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_list,get_list1\n", - "01/16/2026 08:29:59 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: get_list, type: PYFUNCTION, finished.\n", - "01/16/2026 08:29:59 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_list1\n", - "01/16/2026 08:30:00 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: get_list1, type: PYFUNCTION, finished.\n", - "01/16/2026 08:30:00 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: plot_energy_volume_curve\n", - "01/16/2026 08:30:00 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|update_task_state]: Task: plot_energy_volume_curve, type: PYFUNCTION, finished.\n", - "01/16/2026 08:30:00 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n", - "01/16/2026 08:30:01 AM <71653> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [1663|WorkGraphEngine|finalize]: Finalize workgraph.\n" - ] - }, - { - "data": { - "text/plain": [ - "{}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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m5ubIiQkRI5PqTzEuAxdfY3cSZvLcHXpD/G7IspT//3vf+VjRNlJjNU4cuSIQhf7Y8CAAXJG3KFDhxRRUVGK9evXy3FNn3/+uUIblZaWKjw8PORMwTvp0vvpvfqjWAffTxksabCdO3cqOnXqJAdpent7yzf424lBu2+99Zac8ioe8+CDD8pfcl3sj+joaDnmoLpDfBDo6mtEl4KluvbHN998o2jXrp0MCjp37qz47bffFLraH+LDb86cOQoXFxfZHx07dlSsWrWqxunjmm7v3r3yPSE8PPyu+3Tt/bS2/ojWwfdTPfEfVWe3iIiIiNQVxywRERER1YLBEhEREVEtGCwRERER1YLBEhEREVEtGCwRERER1YLBEhEREVEtGCwRERER1YLBEhEREVEtGCwRkU4TmwZ//PHHqm4GEakxBktEpLHGjBmDwYMHV3vf8ePHoaenh7NnzzZ7u4hIuzBYIiKN9cQTT+DgwYOIjY29675vv/0WXbp0Qbdu3VTSNiLSHgyWiEhjjR49Gg4ODtiwYUOV6/n5+fj5559lMLVt2zb4+fnBxMREltxWrVpV4/PFxMTIbFRISIjyWlZWlrx2+PBheVt8Fbf37t2Lrl27wszMDIMGDUJaWhp2794NHx8fWFlZYerUqbIdlcQ2nCtXrkSbNm3k93Tu3Blbt25tkn4hosbFYImINJahoSFmzZolg6Xb9wTfsmULioqKEBgYiMceewxTpkzBxYsXsXTpUrzxxht3BVf1IZ5r7dq1CAoKQnx8vPx3xNinH374AX/++Sf279+PTz/9VPn4119/HevXr8e6detw+fJlLFy4EDNmzMDff//d4LYQUdPSU9z+DkNEpGHCwsJkNkeU4x566CF5bcCAAXB1dZUZoPT0dOzbt0/5+FdeeUUGMyJgEUS26cUXX5SHyCy1bt0a586dkyW8ysxSy5YtcejQIQwcOFBmlsS/c+DAATz88MPyMStWrMDixYtx7do1mTkSnn76afl8e/bsQV5eHuzs7GQbRQBXad68eTL7JAIsIlJfzCwRkUbz9vZG37595RglQQQsR44cweOPP47Q0FD069evyuPF7cjISJSWljbo3w0ICFCeOzo6wtzcXBkoVV4TpTnhypUrKCgowJAhQ9CiRQvlsWnTJtleIlJvhqpuABFRQ4mxSQsWLMBnn30mS12enp4y6yMS5yK7dLvakun6+vp3Paa4uLjaxxoZGSnPxb9x++3Ka2VlZfK88qvIaImM1+3EWCoiUm/MLBGRxhPjhQwMDGQ5a+PGjZg7d64MVnx9fXH06NEqjxVjjDp06CAffyd7e3v5NTk5WXnt9sHe9SXaIYKiuLg4tGvXrsrh7u7e4OcnoqbFzBIRaTxR0po8eTKWLFmC7OxszJkzR15/+eWX0bNnT7z77rvyfrH2khiU/fnnn1f7PGKWWp8+feQYJDGW6fr163JgdkNZWlri3//+txzULbJM/fv3R05OjgzcRNtnz57d4H+DiJoOM0tEpDWluMzMTLlIpYeHh7wm1lj65Zdf8NNPP6FTp05488038c477yiDqeqIsU+i9NajRw+88MILeO+99xqlfSJgE//+8uXL5YD0YcOGYefOnXJAORGpN86GIyIiIqoFM0tEREREtWCwRERERFQLBktEREREtWCwRERERFQLBktEREREtWCwRERERFQLBktEREREtWCwRERERFQLBktEREREtWCwRERERFQLBktEREREtWCwRERERISa/T8ezoucFchGGAAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "wg.run()" + "with open(workflow_json_filename) as _f:\n", + " inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\n", + "inputs['a'] = 4.05\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { @@ -1770,7 +1688,7 @@ "cell_type": "markdown", "id": "406b0429e65b9760", "metadata": {}, - "source": "## Load Workflow with pyiron_workflow\n\nFinally, the graph is loaded into a `pyiron_workflow` `Workflow`, its lattice constant overridden, drawn with `.draw()`, and executed with `.run()`." + "source": "## Load Workflow with pyiron_workflow\n\nFinally, the graph is loaded into a `pyiron_workflow` `Workflow`, its lattice constant overridden, drawn with `.draw()`, and executed with `run_get_node`." }, { "cell_type": "code", diff --git a/example_workflows/quantum_espresso/jobflow.ipynb b/example_workflows/quantum_espresso/jobflow.ipynb index d8b1e1f..5cd414d 100644 --- a/example_workflows/quantum_espresso/jobflow.ipynb +++ b/example_workflows/quantum_espresso/jobflow.ipynb @@ -857,31 +857,24 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": { "trusted": true }, - "outputs": [ - { - "data": { - "text/plain": [ - "Profile" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "from aiida import orm, load_profile\n", + "import json\n", + "\n", + "from aiida import load_profile\n", + "\n", + "from aiida.engine.launch import run_get_node\n", "\n", "load_profile()" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": { "trusted": true }, @@ -892,13 +885,13 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": { "trusted": true }, "outputs": [], "source": [ - "wg = load_workflow_json(workflow_json_filename)" + "graph = load_workflow_json(workflow_json_filename)" ] }, { @@ -910,116 +903,11 @@ }, "outputs": [], "source": [ - "wg.tasks.get_bulk_structure.inputs.a.value = orm.Float(4.05)" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "trusted": true - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "e9bb01de73ff4284a7578d7064ae369f", - "version_major": 2, - "version_minor": 1 - }, - "text/plain": [ - "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'WorkGra…" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "wg" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "05/26/2025 04:59:05 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_bulk_structure1\n", - "05/26/2025 04:59:05 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: get_bulk_structure1, type: PyFunction, finished.\n", - "05/26/2025 04:59:05 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict10\n", - "05/26/2025 04:59:06 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: get_dict10, type: PyFunction, finished.\n", - "05/26/2025 04:59:06 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe2\n", - "[jupyter-pythonworkflow-fl--x---218119f8:02569] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 04:59:56 AM <2497> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['energy', 'volume'])\n", - "05/26/2025 04:59:57 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: calculate_qe2, type: PyFunction, finished.\n", - "05/26/2025 04:59:57 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: generate_structures3\n", - "05/26/2025 04:59:58 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: generate_structures3, type: PyFunction, finished.\n", - "05/26/2025 04:59:58 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict11,get_dict12,get_dict13,get_dict14,get_dict15\n", - "05/26/2025 04:59:58 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: get_dict11, type: PyFunction, finished.\n", - "05/26/2025 04:59:58 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe4,get_dict12,get_dict13,get_dict14,get_dict15\n", - "[jupyter-pythonworkflow-fl--x---218119f8:02586] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:00:10 AM <2497> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 05:00:10 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: calculate_qe4, type: PyFunction, finished.\n", - "05/26/2025 05:00:10 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict12,get_dict13,get_dict14,get_dict15\n", - "05/26/2025 05:00:11 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: get_dict12, type: PyFunction, finished.\n", - "05/26/2025 05:00:11 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe5,get_dict13,get_dict14,get_dict15\n", - "[jupyter-pythonworkflow-fl--x---218119f8:02596] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:00:22 AM <2497> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 05:00:22 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: calculate_qe5, type: PyFunction, finished.\n", - "05/26/2025 05:00:22 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict13,get_dict14,get_dict15\n", - "05/26/2025 05:00:23 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: get_dict13, type: PyFunction, finished.\n", - "05/26/2025 05:00:23 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe6,get_dict14,get_dict15\n", - "[jupyter-pythonworkflow-fl--x---218119f8:02606] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:00:35 AM <2497> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 05:00:35 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: calculate_qe6, type: PyFunction, finished.\n", - "05/26/2025 05:00:35 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict14,get_dict15\n", - "05/26/2025 05:00:35 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: get_dict14, type: PyFunction, finished.\n", - "05/26/2025 05:00:36 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe7,get_dict15\n", - "[jupyter-pythonworkflow-fl--x---218119f8:02616] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:00:50 AM <2497> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 05:00:51 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: calculate_qe7, type: PyFunction, finished.\n", - "05/26/2025 05:00:51 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict15\n", - "05/26/2025 05:00:51 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: get_dict15, type: PyFunction, finished.\n", - "05/26/2025 05:00:51 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe8\n", - "[jupyter-pythonworkflow-fl--x---218119f8:02626] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:01:05 AM <2497> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 05:01:05 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: calculate_qe8, type: PyFunction, finished.\n", - "05/26/2025 05:01:06 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_list16,get_list17\n", - "05/26/2025 05:01:06 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: get_list16, type: PyFunction, finished.\n", - "05/26/2025 05:01:07 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_list17\n", - "05/26/2025 05:01:07 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: get_list17, type: PyFunction, finished.\n", - "05/26/2025 05:01:07 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: plot_energy_volume_curve9\n", - "05/26/2025 05:01:08 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|update_task_state]: Task: plot_energy_volume_curve9, type: PyFunction, finished.\n", - "05/26/2025 05:01:08 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n", - "05/26/2025 05:01:08 AM <2497> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [111|WorkGraphEngine|finalize]: Finalize workgraph.\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAksAAAHACAYAAACyIiyEAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAW/BJREFUeJzt3XlcVXXCx/HPZReUi8omgqC5AO5LKmZp5ZpLWpltmFZOzYwtVlb2tDht2t6UbdOUtttoWrZRmtrkgjuuCLjgwubKIsh6z/MHwkQiKsI9XPi+X6/7eh4u51y+98Rcvv5+5/yOxTAMAxERERGplJPZAURERETqMpUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKqgsmeT555+nX79+eHp64uPjc177GIbBjBkzCAoKolGjRgwcOJAdO3aUfz85ORmLxVLpY/78+We8XkFBAd26dcNisRAXF3dB+d999126dOmCt7c33t7eREVF8dNPP13Qa4iIiDgClSWTFBYWMm7cOP7617+e9z4vvfQSr732GrNnz2b9+vUEBgYyePBgcnJyAAgJCSEtLa3C4x//+AdeXl4MHz78jNd75JFHCAoKqlb+4OBgZs2axYYNG9iwYQNXXXUV1157bYXyJiIiUi8YYqo5c+YYVqv1nNvZbDYjMDDQmDVrVvlz+fn5htVqNd57772z7tetWzfjjjvuOOP5H3/80QgPDzd27NhhAMbmzZsrfH/Hjh3G8OHDDS8vL8Pf39+47bbbjCNHjlSZsWnTpsa///3vc74XERERR6KRJQexb98+0tPTGTJkSPlz7u7uDBgwgNWrV1e6z8aNG4mLi+POO++s8HxGRgaTJ0/m008/xdPT84z90tLSGDBgAN26dWPDhg3ExMSQkZHBjTfeWOnPKSkpYd68eeTm5hIVFXUR71JERKTucTE7gJyf9PR0AAICAio8HxAQwP79+yvd58MPPyQiIoJ+/fqVP2cYBhMnTuSee+6hV69eJCcnn7Hfu+++S48ePXjhhRfKn/voo48ICQkhMTGR9u3bA7Bt2zaioqLIz8+ncePGLFq0iMjIyIt9qyIiInWKRpZq0IwZM856gnXZY8OGDRf1MywWS4WvDcM44zmAU6dO8cUXX5wxqvTWW2+RnZ3N9OnTz/ozNm7cyPLly2ncuHH5Izw8HIA9e/aUb9ehQwfi4uKIjY3lr3/9K7fffjs7d+68mLcnIiJS52hkqQZNmTKFm266qcptwsLCqvXagYGBQOkIU4sWLcqfP3z48BmjTQALFiwgLy+PCRMmVHh+2bJlxMbG4u7uXuH5Xr16ceutt/Lxxx9js9kYNWoUL7744hmv+8ef7ebmRtu2bcv3X79+Pf/85z95//33q/UeRURE6iKVpRrk6+uLr69vrbx269atCQwMZMmSJXTv3h0ovaLut99+q7TUfPjhh4wePRo/P78Kz7/55ps899xz5V+npqYydOhQvvrqK/r06QNAjx49+PrrrwkLC8PF5fx/RQzDoKCgoDpvT0REpM5SWTLJgQMHOH78OAcOHKCkpKR8naO2bdvSuHFjAMLDw5k5cyZjx47FYrHwwAMP8MILL9CuXTvatWvHCy+8gKenJ7fcckuF1969ezf//e9/+fHHH8/4ua1atarwddnPuuSSSwgODgbg73//Ox988AE333wz06ZNw9fXl927dzNv3jw++OADnJ2defzxxxk+fDghISHk5OQwb948VqxYQUxMTE0fKhEREVOpLJnkqaee4uOPPy7/umy0aPny5QwcOBCAhIQEsrKyyrd55JFHOHXqFH/72984ceIEffr04ZdffqFJkyYVXvujjz6iZcuWFa6cuxBBQUGsWrWKRx99lKFDh1JQUEBoaCjDhg3Dyan0NLeMjAyio6NJS0vDarXSpUsXYmJiGDx4cLV+poiISF1lMQzDMDuEiIiISF2lq+FEREREqqCyJCIiIlIFnbNUA2w2G6mpqTRp0qTSNY9ERESk7jEMg5ycHIKCgsrPya2MylINSE1NJSQkxOwYIiIiUg0HDx4svyK8MipLNaDsarSDBw/i7e1tchoRERE5H9nZ2YSEhJxxVfmfqSzVgLKpN29vb5UlERERB3OuU2h0greIiIhIFVSWRERERKqgsiQiIiJSBZUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKqgs1WGGYbBu33HyCovNjiIiItJgqSzVYX/9bBM3vr+GRZtTzI4iIiLSYKks1WG9wpoCMHdVMoZhmJxGRESkYVJZqsNuvDQELzdnkg6fZNXu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Z2z/xhtvYLHo/k4iIiJSkUOs4B0fH09MTAyxsbH06dMHgA8++ICoqCgSEhLo0KHDGfskJSURGxvL9u3b6dixIwDvvPMO/v7+fPnll9x1113l227ZsoXXXnuN9evX06JFC/u8KREREXEIDjGytGbNGqxWa3lRAujbty9Wq5XVq1dXuk9BQQEAHh4e5c85Ozvj5ubGypUry5/Ly8vj5ptvZvbs2QQGBtbSOxARERFH5RBlKT09HX9//zOe9/f3Jz09vdJ9wsPDCQ0NZfr06Zw4cYLCwkJmzZpFeno6aWlp5dtNnTqVfv36ce211553noKCArKzsys8REREpH4ytSzNmDEDi8VS5WPDhg0AlZ5PZBjGWc8zcnV15euvvyYxMZFmzZrh6enJihUrGD58OM7OzgAsXryYZcuW8cYbb1xQ7pkzZ5afaG61WgkJCbmwNy4iIiIOw9RzlqZMmcJNN91U5TZhYWFs3bqVjIyMM7535MgRAgICzrpvz549iYuLIysri8LCQvz8/OjTpw+9evUCYNmyZezZswcfH58K+11//fVcfvnlrFixotLXnT59Og8++GD519nZ2SpMIiIi9ZTFMAzD7BDnEh8fT2RkJGvXrqV3794ArF27lr59+7Jr165KT/CuTFJSEuHh4fz0008MGTKE9PR0jh49WmGbzp07889//pNRo0bRunXr83rd871rsYiIiNQd5/v32yGuhouIiGDYsGFMnjyZ999/H4C//OUvjBw5skJRCg8PZ+bMmYwdOxaA+fPn4+fnR6tWrdi2bRv3338/Y8aMYciQIQAEBgZWelJ3q1atzrsoQel0IKBzl0RERBxI2d/tc40bOURZAvj888+57777yovO6NGjmT17doVtEhISyMrKKv86LS2NBx98kIyMDFq0aMGECRN48sknazxbTk4OgKbiREREHFBOTg5Wq/Ws33eIabi6zmazkZqaSpMmTerEwpZl51AdPHhQ04LoePyZjseZdEwq0vGoSMejovp0PAzDICcnh6CgIJyczn7Nm8OMLNVlTk5OBAcHmx3jDN7e3g7/i1yTdDwq0vE4k45JRToeFel4VFRfjkdVI0plHGKdJRERERGzqCyJiIiIVEFlqR5yd3fn6aefxt3d3ewodYKOR0U6HmfSMalIx6MiHY+KGuLx0AneIiIiIlXQyJKIiIhIFVSWRERERKqg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WRIRERGpgsqSiIiISBVUlkRERESqoLIkIiIiUgWVJREREZEqqCyJiIiIVEFlSUQatLCwMN544w2zY4hIHaayJCIOa9SoUQwaNKjS761ZswaLxcKmTZvsnEpE6huVJRFxWHfeeSfLli1j//79Z3zvo48+olu3bvTo0cOEZCJSn6gsiYjDGjlyJP7+/sydO7fC83l5eXz11VfceeedfP3113Ts2BF3d3fCwsJ49dVXz/p6ycnJWCwW4uLiyp/LzMzEYrGwYsUKAFasWIHFYuHnn3+me/fuNGrUiKuuuorDhw/z008/ERERgbe3NzfffDN5eXnlr2MYBi+99BJt2rShUaNGdO3alQULFtTk4RCRWqKyJCIOy8XFhQkTJjB37lz+eE/w+fPnU1hYSFRUFDfeeCM33XQT27ZtY8aMGTz55JNnlKvqmDFjBrNnz2b16tUcPHiQG2+8kTfeeIMvvviCH374gSVLlvDWW2+Vb//EE08wZ84c3n33XXbs2MHUqVO57bbb+O233y46i4jULovxx08YEREHs2vXLiIiIli2bBlXXnklAAMGDKBly5ZYLBaOHDnCL7/8Ur79I488wg8//MCOHTuA0hO8H3jgAR544AGSk5Np3bo1mzdvplu3bkDpyFLTpk1Zvnw5AwcOZMWKFVx55ZUsXbqUq6++GoBZs2Yxffp09uzZQ5s2bQC45557SE5OJiYmhtzcXHx9fVm2bBlRUVHlWe666y7y8vL44osv7HGoRKSaNLIkIg4tPDycfv368dFHHwGwZ88efv/9d+644w7i4+O57LLLKmx/2WWXkZSURElJyUX93C5dupT//wEBAXh6epYXpbLnDh8+DMDOnTvJz89n8ODBNG7cuPzxySefsGfPnovKISK1z8XsACIiF+vOO+9kypQpvP3228yZM4fQ0FCuvvpqDMPAYrFU2LaqwXQnJ6cztikqKqp0W1dX1/L/32KxVPi67DmbzQZQ/n9/+OEHWrZsWWE7d3f3c709ETGZRpZExOHdeOONODs788UXX/Dxxx8zadIkLBYLkZGRrFy5ssK2q1evpn379jg7O5/xOn5+fgCkpaWVP/fHk72rKzIyEnd3dw4cOEDbtm0rPEJCQi769UWkdmlkSUQcXuPGjRk/fjyPP/44WVlZTJw4EYCHHnqISy+9lGeffZbx48ezZs0aZs+ezTvvvFPp6zRq1Ii+ffsya9YswsLCOHr0KE888cRF52vSpAkPP/wwU6dOxWaz0b9/f7Kzs1m9ejWNGzfm9ttvv+ifISK1RyNLIlIv3HnnnZw4cYJBgwbRqlUrAHr06MF//vMf5s2bR6dOnXjqqad45plnystUZT766COKioro1asX999/P88991yN5Hv22Wd56qmnmDlzJhEREQwdOpTvvvuO1q1b18jri0jt0dVwIiIiIlXQyJKIiIhIFVSWRERERKqgsiQiIiJSBZUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKvw/FsCLm/9axZsAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "wg.run()" + "with open(workflow_json_filename) as _f:\n", + " inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\n", + "inputs['a'] = 4.05\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { diff --git a/example_workflows/quantum_espresso/pyiron_base.ipynb b/example_workflows/quantum_espresso/pyiron_base.ipynb index 1369193..f3c6e11 100644 --- a/example_workflows/quantum_espresso/pyiron_base.ipynb +++ b/example_workflows/quantum_espresso/pyiron_base.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "id": "be2d61b0-0d47-4349-b4b0-1b767c961644", "metadata": {}, - "source": "# pyiron\n\nThis notebook builds the Quantum Espresso energy-volume-curve workflow with [`pyiron_base`](https://github.com/pyiron/pyiron_base). The `job` decorator turns plain functions into delayed pyiron jobs that are only executed once `.pull()` is called on the final result, and the whole chain of jobs is exported as a PWD `workflow.json`. The second half of the notebook loads that same JSON with `aiida-workgraph`, `jobflow` and `pyiron_workflow`." + "source": "# pyiron\n\nThis notebook builds the Quantum Espresso energy-volume-curve workflow with [`pyiron_base`](https://github.com/pyiron/pyiron_base). The `job` decorator turns plain functions into delayed pyiron jobs that are only executed once `.pull()` is called on the final result, and the whole chain of jobs is exported as a PWD `workflow.json`. The second half of the notebook loads that same JSON with `aiida-core`, `jobflow` and `pyiron_workflow`." }, { "cell_type": "markdown", @@ -787,36 +787,29 @@ "cell_type": "markdown", "id": "7d75a2f6-6fad-49c8-bd29-37cca1b84441", "metadata": {}, - "source": "## Load Workflow with aiida\n\n`load_workflow_json` reconstructs the graph as an `aiida-workgraph` `WorkGraph`. The lattice constant `a` is overridden (4.04 -> 4.05) to show the loaded graph can still be edited like one built natively with `aiida-workgraph`, before running it with `wg.run()`." + "source": "## Load Workflow with aiida\n\n`load_workflow_json` reconstructs the graph as an `aiida-core` `GraphHandle`. The lattice constant `a` is overridden (4.04 -> 4.05) to show the loaded graph can still be edited like one built natively with `aiida-core`, before running it with `run_get_node`." }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "28760464-2a2c-40c2-9451-20713da2ba2c", "metadata": { "trusted": true }, - "outputs": [ - { - "data": { - "text/plain": [ - "Profile" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "from aiida import orm, load_profile\n", + "import json\n", + "\n", + "from aiida import load_profile\n", + "\n", + "from aiida.engine.launch import run_get_node\n", "\n", "load_profile()" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "a442fe46-057c-469b-a5e0-173ed4829dfb", "metadata": { "trusted": true @@ -828,14 +821,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "32fcd4b2-4f0a-442d-b098-827672823796", "metadata": { "trusted": true }, "outputs": [], "source": [ - "wg = load_workflow_json(file_name=workflow_json_filename)" + "graph = load_workflow_json(file_name=workflow_json_filename)" ] }, { @@ -847,90 +840,11 @@ }, "outputs": [], "source": [ - "wg.tasks.get_bulk_structure.inputs.a.value = orm.Float(4.05)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "a80b59bd-fe30-49c6-92ca-35ef2d77a6fb", - "metadata": { - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "05/26/2025 04:46:41 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_bulk_structure8\n", - "05/26/2025 04:46:41 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_bulk_structure8, type: PyFunction, finished.\n", - "05/26/2025 04:46:41 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict7\n", - "05/26/2025 04:46:42 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict7, type: PyFunction, finished.\n", - "05/26/2025 04:46:42 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe6\n", - "[jupyter-pythonworkflow-fl--x---218119f8:01776] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 04:47:34 AM <1688> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['energy', 'volume'])\n", - "05/26/2025 04:47:34 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe6, type: PyFunction, finished.\n", - "05/26/2025 04:47:34 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: generate_structures5\n", - "05/26/2025 04:47:35 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: generate_structures5, type: PyFunction, finished.\n", - "05/26/2025 04:47:35 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict4,get_dict10,get_dict12,get_dict14,get_dict16\n", - "05/26/2025 04:47:36 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict4, type: PyFunction, finished.\n", - "05/26/2025 04:47:36 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe3,get_dict10,get_dict12,get_dict14,get_dict16\n", - "[jupyter-pythonworkflow-fl--x---218119f8:01828] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 04:47:47 AM <1688> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 04:47:47 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe3, type: PyFunction, finished.\n", - "05/26/2025 04:47:47 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict10,get_dict12,get_dict14,get_dict16\n", - "05/26/2025 04:47:48 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict10, type: PyFunction, finished.\n", - "05/26/2025 04:47:48 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe9,get_dict12,get_dict14,get_dict16\n", - "[jupyter-pythonworkflow-fl--x---218119f8:01860] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 04:47:59 AM <1688> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 04:48:00 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe9, type: PyFunction, finished.\n", - "05/26/2025 04:48:00 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict12,get_dict14,get_dict16\n", - "05/26/2025 04:48:00 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict12, type: PyFunction, finished.\n", - "05/26/2025 04:48:00 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe11,get_dict14,get_dict16\n", - "[jupyter-pythonworkflow-fl--x---218119f8:01874] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 04:48:13 AM <1688> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 04:48:13 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe11, type: PyFunction, finished.\n", - "05/26/2025 04:48:13 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict14,get_dict16\n", - "05/26/2025 04:48:14 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict14, type: PyFunction, finished.\n", - "05/26/2025 04:48:14 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe13,get_dict16\n", - "[jupyter-pythonworkflow-fl--x---218119f8:01885] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 04:48:29 AM <1688> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 04:48:29 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe13, type: PyFunction, finished.\n", - "05/26/2025 04:48:29 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict16\n", - "05/26/2025 04:48:29 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict16, type: PyFunction, finished.\n", - "05/26/2025 04:48:29 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe15\n", - "[jupyter-pythonworkflow-fl--x---218119f8:01895] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 04:48:44 AM <1688> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 04:48:44 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe15, type: PyFunction, finished.\n", - "05/26/2025 04:48:44 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_list2,get_list17\n", - "05/26/2025 04:48:44 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_list2, type: PyFunction, finished.\n", - "05/26/2025 04:48:44 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_list17\n", - "05/26/2025 04:48:45 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_list17, type: PyFunction, finished.\n", - "05/26/2025 04:48:45 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: plot_energy_volume_curve1\n", - "05/26/2025 04:48:45 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: plot_energy_volume_curve1, type: PyFunction, finished.\n", - "05/26/2025 04:48:46 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n", - "05/26/2025 04:48:46 AM <1688> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|finalize]: Finalize workgraph.\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAksAAAHACAYAAACyIiyEAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAW/BJREFUeJzt3XlcVXXCx/HPZReUi8omgqC5AO5LKmZp5ZpLWpltmFZOzYwtVlb2tDht2t6UbdOUtttoWrZRmtrkgjuuCLjgwubKIsh6z/MHwkQiKsI9XPi+X6/7eh4u51y+98Rcvv5+5/yOxTAMAxERERGplJPZAURERETqMpUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKqgsmeT555+nX79+eHp64uPjc177GIbBjBkzCAoKolGjRgwcOJAdO3aUfz85ORmLxVLpY/78+We8XkFBAd26dcNisRAXF3dB+d999126dOmCt7c33t7eREVF8dNPP13Qa4iIiDgClSWTFBYWMm7cOP7617+e9z4vvfQSr732GrNnz2b9+vUEBgYyePBgcnJyAAgJCSEtLa3C4x//+AdeXl4MHz78jNd75JFHCAoKqlb+4OBgZs2axYYNG9iwYQNXXXUV1157bYXyJiIiUi8YYqo5c+YYVqv1nNvZbDYjMDDQmDVrVvlz+fn5htVqNd57772z7tetWzfjjjvuOOP5H3/80QgPDzd27NhhAMbmzZsrfH/Hjh3G8OHDDS8vL8Pf39+47bbbjCNHjlSZsWnTpsa///3vc74XERERR6KRJQexb98+0tPTGTJkSPlz7u7uDBgwgNWrV1e6z8aNG4mLi+POO++s8HxGRgaTJ0/m008/xdPT84z90tLSGDBgAN26dWPDhg3ExMSQkZHBjTfeWOnPKSkpYd68eeTm5hIVFXUR71JERKTucTE7gJyf9PR0AAICAio8HxAQwP79+yvd58MPPyQiIoJ+/fqVP2cYBhMnTuSee+6hV69eJCcnn7Hfu+++S48ePXjhhRfKn/voo48ICQkhMTGR9u3bA7Bt2zaioqLIz8+ncePGLFq0iMjIyIt9qyIiInWKRpZq0IwZM856gnXZY8OGDRf1MywWS4WvDcM44zmAU6dO8cUXX5wxqvTWW2+RnZ3N9OnTz/ozNm7cyPLly2ncuHH5Izw8HIA9e/aUb9ehQwfi4uKIjY3lr3/9K7fffjs7d+68mLcnIiJS52hkqQZNmTKFm266qcptwsLCqvXagYGBQOkIU4sWLcqfP3z48BmjTQALFiwgLy+PCRMmVHh+2bJlxMbG4u7uXuH5Xr16ceutt/Lxxx9js9kYNWoUL7744hmv+8ef7ebmRtu2bcv3X79+Pf/85z95//33q/UeRURE6iKVpRrk6+uLr69vrbx269atCQwMZMmSJXTv3h0ovaLut99+q7TUfPjhh4wePRo/P78Kz7/55ps899xz5V+npqYydOhQvvrqK/r06QNAjx49+PrrrwkLC8PF5fx/RQzDoKCgoDpvT0REpM5SWTLJgQMHOH78OAcOHKCkpKR8naO2bdvSuHFjAMLDw5k5cyZjx47FYrHwwAMP8MILL9CuXTvatWvHCy+8gKenJ7fcckuF1969ezf//e9/+fHHH8/4ua1atarwddnPuuSSSwgODgbg73//Ox988AE333wz06ZNw9fXl927dzNv3jw++OADnJ2defzxxxk+fDghISHk5OQwb948VqxYQUxMTE0fKhEREVOpLJnkqaee4uOPPy7/umy0aPny5QwcOBCAhIQEsrKyyrd55JFHOHXqFH/72984ceIEffr04ZdffqFJkyYVXvujjz6iZcuWFa6cuxBBQUGsWrWKRx99lKFDh1JQUEBoaCjDhg3Dyan0NLeMjAyio6NJS0vDarXSpUsXYmJiGDx4cLV+poiISF1lMQzDMDuEiIiISF2lq+FEREREqqCyJCIiIlIFnbNUA2w2G6mpqTRp0qTSNY9ERESk7jEMg5ycHIKCgsrPya2MylINSE1NJSQkxOwYIiIiUg0HDx4svyK8MipLNaDsarSDBw/i7e1tchoRERE5H9nZ2YSEhJxxVfmfqSzVgLKpN29vb5UlERERB3OuU2h0greIiIhIFVSWRERERKqgsiQiIiJSBZUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKqgs1WGGYbBu33HyCovNjiIiItJgqSzVYX/9bBM3vr+GRZtTzI4iIiLSYKks1WG9wpoCMHdVMoZhmJxGRESkYVJZqsNuvDQELzdnkg6fZNXu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Z2z/xhtvYLHo/k4iIiJSkUOs4B0fH09MTAyxsbH06dMHgA8++ICoqCgSEhLo0KHDGfskJSURGxvL9u3b6dixIwDvvPMO/v7+fPnll9x1113l227ZsoXXXnuN9evX06JFC/u8KREREXEIDjGytGbNGqxWa3lRAujbty9Wq5XVq1dXuk9BQQEAHh4e5c85Ozvj5ubGypUry5/Ly8vj5ptvZvbs2QQGBtbSOxARERFH5RBlKT09HX9//zOe9/f3Jz09vdJ9wsPDCQ0NZfr06Zw4cYLCwkJmzZpFeno6aWlp5dtNnTqVfv36ce211553noKCArKzsys8REREpH4ytSzNmDEDi8VS5WPDhg0AlZ5PZBjGWc8zcnV15euvvyYxMZFmzZrh6enJihUrGD58OM7OzgAsXryYZcuW8cYbb1xQ7pkzZ5afaG61WgkJCbmwNy4iIiIOw9RzlqZMmcJNN91U5TZhYWFs3bqVjIyMM7535MgRAgICzrpvz549iYuLIysri8LCQvz8/OjTpw+9evUCYNmyZezZswcfH58K+11//fVcfvnlrFixotLXnT59Og8++GD519nZ2SpMIiIi9ZTFMAzD7BDnEh8fT2RkJGvXrqV3794ArF27lr59+7Jr165KT/CuTFJSEuHh4fz0008MGTKE9PR0jh49WmGbzp07889//pNRo0bRunXr83rd871rsYiIiNQd5/v32yGuhouIiGDYsGFMnjyZ999/H4C//OUvjBw5skJRCg8PZ+bMmYwdOxaA+fPn4+fnR6tWrdi2bRv3338/Y8aMYciQIQAEBgZWelJ3q1atzrsoQel0IKBzl0RERBxI2d/tc40bOURZAvj888+57777yovO6NGjmT17doVtEhISyMrKKv86LS2NBx98kIyMDFq0aMGECRN48sknazxbTk4OgKbiREREHFBOTg5Wq/Ws33eIabi6zmazkZqaSpMmTerEwpZl51AdPHhQ04LoePyZjseZdEwq0vGoSMejovp0PAzDICcnh6CgIJyczn7Nm8OMLNVlTk5OBAcHmx3jDN7e3g7/i1yTdDwq0vE4k45JRToeFel4VFRfjkdVI0plHGKdJRERERGzqCyJiIiIVEFlqR5yd3fn6aefxt3d3ewodYKOR0U6HmfSMalIx6MiHY+KGuLx0AneIiIiIlXQyJKIiIhIFVSWRERERKqg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WRIRERGpgsqSiIiISBVUlkRERESqoLIkIiIiUgWVJREREZEqqCyJiIiIVEFlSUQatLCwMN544w2zY4hIHaayJCIOa9SoUQwaNKjS761ZswaLxcKmTZvsnEpE6huVJRFxWHfeeSfLli1j//79Z3zvo48+olu3bvTo0cOEZCJSn6gsiYjDGjlyJP7+/sydO7fC83l5eXz11VfceeedfP3113Ts2BF3d3fCwsJ49dVXz/p6ycnJWCwW4uLiyp/LzMzEYrGwYsUKAFasWIHFYuHnn3+me/fuNGrUiKuuuorDhw/z008/ERERgbe3NzfffDN5eXnlr2MYBi+99BJt2rShUaNGdO3alQULFtTk4RCRWqKyJCIOy8XFhQkTJjB37lz+eE/w+fPnU1hYSFRUFDfeeCM33XQT27ZtY8aMGTz55JNnlKvqmDFjBrNnz2b16tUcPHiQG2+8kTfeeIMvvviCH374gSVLlvDWW2+Vb//EE08wZ84c3n33XXbs2MHUqVO57bbb+O233y46i4jULovxx08YEREHs2vXLiIiIli2bBlXXnklAAMGDKBly5ZYLBaOHDnCL7/8Ur79I488wg8//MCOHTuA0hO8H3jgAR544AGSk5Np3bo1mzdvplu3bkDpyFLTpk1Zvnw5AwcOZMWKFVx55ZUsXbqUq6++GoBZs2Yxffp09uzZQ5s2bQC45557SE5OJiYmhtzcXHx9fVm2bBlRUVHlWe666y7y8vL44osv7HGoRKSaNLIkIg4tPDycfv368dFHHwGwZ88efv/9d+644w7i4+O57LLLKmx/2WWXkZSURElJyUX93C5dupT//wEBAXh6epYXpbLnDh8+DMDOnTvJz89n8ODBNG7cuPzxySefsGfPnovKISK1z8XsACIiF+vOO+9kypQpvP3228yZM4fQ0FCuvvpqDMPAYrFU2LaqwXQnJ6cztikqKqp0W1dX1/L/32KxVPi67DmbzQZQ/n9/+OEHWrZsWWE7d3f3c709ETGZRpZExOHdeOONODs788UXX/Dxxx8zadIkLBYLkZGRrFy5ssK2q1evpn379jg7O5/xOn5+fgCkpaWVP/fHk72rKzIyEnd3dw4cOEDbtm0rPEJCQi769UWkdmlkSUQcXuPGjRk/fjyPP/44WVlZTJw4EYCHHnqISy+9lGeffZbx48ezZs0aZs+ezTvvvFPp6zRq1Ii+ffsya9YswsLCOHr0KE888cRF52vSpAkPP/wwU6dOxWaz0b9/f7Kzs1m9ejWNGzfm9ttvv+ifISK1RyNLIlIv3HnnnZw4cYJBgwbRqlUrAHr06MF//vMf5s2bR6dOnXjqqad45plnystUZT766COKioro1asX999/P88991yN5Hv22Wd56qmnmDlzJhEREQwdOpTvvvuO1q1b18jri0jt0dVwIiIiIlXQyJKIiIhIFVSWRERERKqgsiQiIiJSBZUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKvw/FsCLm/9axZsAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "wg.run()" + "with open(workflow_json_filename) as _f:\n", + " inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\n", + "inputs['a'] = 4.05\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { @@ -1173,7 +1087,7 @@ "cell_type": "markdown", "id": "406b0429e65b9760", "metadata": {}, - "source": "## Load Workflow with pyiron_workflow\n\nFinally, the graph is loaded into a `pyiron_workflow` `Workflow`, its lattice constant overridden, drawn with `.draw()`, and executed with `.run()`." + "source": "## Load Workflow with pyiron_workflow\n\nFinally, the graph is loaded into a `pyiron_workflow` `Workflow`, its lattice constant overridden, drawn with `.draw()`, and executed with `run_get_node`." }, { "cell_type": "code", diff --git a/example_workflows/quantum_espresso/pyiron_workflow.ipynb b/example_workflows/quantum_espresso/pyiron_workflow.ipynb index 88d94a2..3df28f6 100644 --- a/example_workflows/quantum_espresso/pyiron_workflow.ipynb +++ b/example_workflows/quantum_espresso/pyiron_workflow.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "id": "760acc89-8c02-4bc9-a8f6-2572506b7085", "metadata": {}, - "source": "# pyiron_workflow\n## Define workflow with pyiron_workflow\n\nThis notebook builds the Quantum Espresso energy-volume-curve workflow with [`pyiron_workflow`](https://github.com/pyiron/pyiron_workflow). Plain functions are converted to graph nodes with `to_function_node`, assembled into a `Workflow` via attribute access, and exported as a PWD `workflow.json`. The second half of the notebook loads that same JSON with `aiida-workgraph`, `jobflow` and `pyiron_base`." + "source": "# pyiron_workflow\n## Define workflow with pyiron_workflow\n\nThis notebook builds the Quantum Espresso energy-volume-curve workflow with [`pyiron_workflow`](https://github.com/pyiron/pyiron_workflow). Plain functions are converted to graph nodes with `to_function_node`, assembled into a `Workflow` via attribute access, and exported as a PWD `workflow.json`. The second half of the notebook loads that same JSON with `aiida-core`, `jobflow` and `pyiron_base`." }, { "cell_type": "code", @@ -4721,36 +4721,29 @@ "cell_type": "markdown", "id": "fef7107a-5af3-4434-ae4c-a8d45e1d9b61", "metadata": {}, - "source": "## Load Workflow with aiida\n\n`load_workflow_json` reconstructs the graph as an `aiida-workgraph` `WorkGraph`. The lattice constant `a` is overridden (4.04 -> 4.05) to show the loaded graph can still be edited like one built natively with `aiida-workgraph`, before running it with `wg.run()`." + "source": "## Load Workflow with aiida\n\n`load_workflow_json` reconstructs the graph as an `aiida-core` `GraphHandle`. The lattice constant `a` is overridden (4.04 -> 4.05) to show the loaded graph can still be edited like one built natively with `aiida-core`, before running it with `run_get_node`." }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "5f4d2243-faf6-4d57-be70-5f7d1292e33e", "metadata": { "trusted": true }, - "outputs": [ - { - "data": { - "text/plain": [ - "Profile" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "from aiida import orm, load_profile\n", + "import json\n", + "\n", + "from aiida import load_profile\n", + "\n", + "from aiida.engine.launch import run_get_node\n", "\n", "load_profile()" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "232c9447-8274-4a82-9c8c-468af7ec6c43", "metadata": { "trusted": true @@ -4762,14 +4755,14 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "97150c3d-dd11-4b24-8e19-a088bb85a2c8", "metadata": { "trusted": true }, "outputs": [], "source": [ - "wg = load_workflow_json(file_name=workflow_json_filename)" + "graph = load_workflow_json(file_name=workflow_json_filename)" ] }, { @@ -4781,90 +4774,11 @@ }, "outputs": [], "source": [ - "wg.tasks.get_bulk_structure.inputs.a.value = orm.Float(4.05)" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "dac6c4ad-297e-43b1-8d0a-ea841f3d6aa7", - "metadata": { - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "05/26/2025 05:57:55 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_bulk_structure1\n", - "05/26/2025 05:57:56 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_bulk_structure1, type: PyFunction, finished.\n", - "05/26/2025 05:57:56 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict2\n", - "05/26/2025 05:57:56 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict2, type: PyFunction, finished.\n", - "05/26/2025 05:57:56 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe3\n", - "[jupyter-pythonworkflow-fl--x---35e2fba5:00217] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:58:48 AM <86> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['energy', 'volume'])\n", - "05/26/2025 05:58:48 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe3, type: PyFunction, finished.\n", - "05/26/2025 05:58:48 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: generate_structures4\n", - "05/26/2025 05:58:49 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: generate_structures4, type: PyFunction, finished.\n", - "05/26/2025 05:58:49 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict5,get_dict7,get_dict9,get_dict11,get_dict13\n", - "05/26/2025 05:58:49 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict5, type: PyFunction, finished.\n", - "05/26/2025 05:58:50 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe6,get_dict7,get_dict9,get_dict11,get_dict13\n", - "[jupyter-pythonworkflow-fl--x---35e2fba5:00262] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:59:03 AM <86> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 05:59:04 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe6, type: PyFunction, finished.\n", - "05/26/2025 05:59:04 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict7,get_dict9,get_dict11,get_dict13\n", - "05/26/2025 05:59:04 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict7, type: PyFunction, finished.\n", - "05/26/2025 05:59:05 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe8,get_dict9,get_dict11,get_dict13\n", - "[jupyter-pythonworkflow-fl--x---35e2fba5:00273] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:59:16 AM <86> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 05:59:16 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe8, type: PyFunction, finished.\n", - "05/26/2025 05:59:16 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict9,get_dict11,get_dict13\n", - "05/26/2025 05:59:17 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict9, type: PyFunction, finished.\n", - "05/26/2025 05:59:17 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe10,get_dict11,get_dict13\n", - "[jupyter-pythonworkflow-fl--x---35e2fba5:00285] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:59:28 AM <86> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 05:59:29 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe10, type: PyFunction, finished.\n", - "05/26/2025 05:59:29 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict11,get_dict13\n", - "05/26/2025 05:59:29 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict11, type: PyFunction, finished.\n", - "05/26/2025 05:59:29 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe12,get_dict13\n", - "[jupyter-pythonworkflow-fl--x---35e2fba5:00296] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:59:43 AM <86> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 05:59:43 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe12, type: PyFunction, finished.\n", - "05/26/2025 05:59:43 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict13\n", - "05/26/2025 05:59:44 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_dict13, type: PyFunction, finished.\n", - "05/26/2025 05:59:44 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe14\n", - "[jupyter-pythonworkflow-fl--x---35e2fba5:00307] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\n", - "Note: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n", - "05/26/2025 05:59:58 AM <86> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n", - "05/26/2025 05:59:59 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: calculate_qe14, type: PyFunction, finished.\n", - "05/26/2025 05:59:59 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_list15,get_list16\n", - "05/26/2025 05:59:59 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_list15, type: PyFunction, finished.\n", - "05/26/2025 05:59:59 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_list16\n", - "05/26/2025 06:00:00 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: get_list16, type: PyFunction, finished.\n", - "05/26/2025 06:00:00 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: plot_energy_volume_curve17\n", - "05/26/2025 06:00:01 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|update_task_state]: Task: plot_energy_volume_curve17, type: PyFunction, finished.\n", - "05/26/2025 06:00:01 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n", - "05/26/2025 06:00:01 AM <86> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [17|WorkGraphEngine|finalize]: Finalize workgraph.\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAksAAAHACAYAAACyIiyEAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAW/BJREFUeJzt3XlcVXXCx/HPZReUi8omgqC5AO5LKmZp5ZpLWpltmFZOzYwtVlb2tDht2t6UbdOUtttoWrZRmtrkgjuuCLjgwubKIsh6z/MHwkQiKsI9XPi+X6/7eh4u51y+98Rcvv5+5/yOxTAMAxERERGplJPZAURERETqMpUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKqgsmeT555+nX79+eHp64uPjc177GIbBjBkzCAoKolGjRgwcOJAdO3aUfz85ORmLxVLpY/78+We8XkFBAd26dcNisRAXF3dB+d999126dOmCt7c33t7eREVF8dNPP13Qa4iIiDgClSWTFBYWMm7cOP7617+e9z4vvfQSr732GrNnz2b9+vUEBgYyePBgcnJyAAgJCSEtLa3C4x//+AdeXl4MHz78jNd75JFHCAoKqlb+4OBgZs2axYYNG9iwYQNXXXUV1157bYXyJiIiUi8YYqo5c+YYVqv1nNvZbDYjMDDQmDVrVvlz+fn5htVqNd57772z7tetWzfjjjvuOOP5H3/80QgPDzd27NhhAMbmzZsrfH/Hjh3G8OHDDS8vL8Pf39+47bbbjCNHjlSZsWnTpsa///3vc74XERERR6KRJQexb98+0tPTGTJkSPlz7u7uDBgwgNWrV1e6z8aNG4mLi+POO++s8HxGRgaTJ0/m008/xdPT84z90tLSGDBgAN26dWPDhg3ExMSQkZHBjTfeWOnPKSkpYd68eeTm5hIVFXUR71JERKTucTE7gJyf9PR0AAICAio8HxAQwP79+yvd58MPPyQiIoJ+/fqVP2cYBhMnTuSee+6hV69eJCcnn7Hfu+++S48ePXjhhRfKn/voo48ICQkhMTGR9u3bA7Bt2zaioqLIz8+ncePGLFq0iMjIyIt9qyIiInWKRpZq0IwZM856gnXZY8OGDRf1MywWS4WvDcM44zmAU6dO8cUXX5wxqvTWW2+RnZ3N9OnTz/ozNm7cyPLly2ncuHH5Izw8HIA9e/aUb9ehQwfi4uKIjY3lr3/9K7fffjs7d+68mLcnIiJS52hkqQZNmTKFm266qcptwsLCqvXagYGBQOkIU4sWLcqfP3z48BmjTQALFiwgLy+PCRMmVHh+2bJlxMbG4u7uXuH5Xr16ceutt/Lxxx9js9kYNWoUL7744hmv+8ef7ebmRtu2bcv3X79+Pf/85z95//33q/UeRURE6iKVpRrk6+uLr69vrbx269atCQwMZMmSJXTv3h0ovaLut99+q7TUfPjhh4wePRo/P78Kz7/55ps899xz5V+npqYydOhQvvrqK/r06QNAjx49+PrrrwkLC8PF5fx/RQzDoKCgoDpvT0REpM5SWTLJgQMHOH78OAcOHKCkpKR8naO2bdvSuHFjAMLDw5k5cyZjx47FYrHwwAMP8MILL9CuXTvatWvHCy+8gKenJ7fcckuF1969ezf//e9/+fHHH8/4ua1atarwddnPuuSSSwgODgbg73//Ox988AE333wz06ZNw9fXl927dzNv3jw++OADnJ2defzxxxk+fDghISHk5OQwb948VqxYQUxMTE0fKhEREVOpLJnkqaee4uOPPy7/umy0aPny5QwcOBCAhIQEsrKyyrd55JFHOHXqFH/72984ceIEffr04ZdffqFJkyYVXvujjz6iZcuWFa6cuxBBQUGsWrWKRx99lKFDh1JQUEBoaCjDhg3Dyan0NLeMjAyio6NJS0vDarXSpUsXYmJiGDx4cLV+poiISF1lMQzDMDuEiIiISF2lq+FEREREqqCyJCIiIlIFnbNUA2w2G6mpqTRp0qTSNY9ERESk7jEMg5ycHIKCgsrPya2MylINSE1NJSQkxOwYIiIiUg0HDx4svyK8MipLNaDsarSDBw/i7e1tchoRERE5H9nZ2YSEhJxxVfmfqSzVgLKpN29vb5UlERERB3OuU2h0greIiIhIFVSWRERERKqgsiQiIiJSBZUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKqgs1WGGYbBu33HyCovNjiIiItJgqSzVYX/9bBM3vr+GRZtTzI4iIiLSYKks1WG9wpoCMHdVMoZhmJxGRESkYVJZqsNuvDQELzdnkg6fZNXu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Z2z/xhtvYLHo/k4iIiJSkUOs4B0fH09MTAyxsbH06dMHgA8++ICoqCgSEhLo0KHDGfskJSURGxvL9u3b6dixIwDvvPMO/v7+fPnll9x1113l227ZsoXXXnuN9evX06JFC/u8KREREXEIDjGytGbNGqxWa3lRAujbty9Wq5XVq1dXuk9BQQEAHh4e5c85Ozvj5ubGypUry5/Ly8vj5ptvZvbs2QQGBtbSOxARERFH5RBlKT09HX9//zOe9/f3Jz09vdJ9wsPDCQ0NZfr06Zw4cYLCwkJmzZpFeno6aWlp5dtNnTqVfv36ce211553noKCArKzsys8REREpH4ytSzNmDEDi8VS5WPDhg0AlZ5PZBjGWc8zcnV15euvvyYxMZFmzZrh6enJihUrGD58OM7OzgAsXryYZcuW8cYbb1xQ7pkzZ5afaG61WgkJCbmwNy4iIiIOw9RzlqZMmcJNN91U5TZhYWFs3bqVjIyMM7535MgRAgICzrpvz549iYuLIysri8LCQvz8/OjTpw+9evUCYNmyZezZswcfH58K+11//fVcfvnlrFixotLXnT59Og8++GD519nZ2SpMIiIi9ZTFMAzD7BDnEh8fT2RkJGvXrqV3794ArF27lr59+7Jr165KT/CuTFJSEuHh4fz0008MGTKE9PR0jh49WmGbzp07889//pNRo0bRunXr83rd871rsYiIiNQd5/v32yGuhouIiGDYsGFMnjyZ999/H4C//OUvjBw5skJRCg8PZ+bMmYwdOxaA+fPn4+fnR6tWrdi2bRv3338/Y8aMYciQIQAEBgZWelJ3q1atzrsoQel0IKBzl0RERBxI2d/tc40bOURZAvj888+57777yovO6NGjmT17doVtEhISyMrKKv86LS2NBx98kIyMDFq0aMGECRN48sknazxbTk4OgKbiREREHFBOTg5Wq/Ws33eIabi6zmazkZqaSpMmTerEwpZl51AdPHhQ04LoePyZjseZdEwq0vGoSMejovp0PAzDICcnh6CgIJyczn7Nm8OMLNVlTk5OBAcHmx3jDN7e3g7/i1yTdDwq0vE4k45JRToeFel4VFRfjkdVI0plHGKdJRERERGzqCyJiIiIVEFlqR5yd3fn6aefxt3d3ewodYKOR0U6HmfSMalIx6MiHY+KGuLx0AneIiIiIlXQyJKIiIhIFVSWRERERKqg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WRIRERGpgsqSiIiISBVUlkRERESqoLIkIiIiUgWVJREREZEqqCyJiIiIVEFlSUQatLCwMN544w2zY4hIHaayJCIOa9SoUQwaNKjS761ZswaLxcKmTZvsnEpE6huVJRFxWHfeeSfLli1j//79Z3zvo48+olu3bvTo0cOEZCJSn6gsiYjDGjlyJP7+/sydO7fC83l5eXz11VfceeedfP3113Ts2BF3d3fCwsJ49dVXz/p6ycnJWCwW4uLiyp/LzMzEYrGwYsUKAFasWIHFYuHnn3+me/fuNGrUiKuuuorDhw/z008/ERERgbe3NzfffDN5eXnlr2MYBi+99BJt2rShUaNGdO3alQULFtTk4RCRWqKyJCIOy8XFhQkTJjB37lz+eE/w+fPnU1hYSFRUFDfeeCM33XQT27ZtY8aMGTz55JNnlKvqmDFjBrNnz2b16tUcPHiQG2+8kTfeeIMvvviCH374gSVLlvDWW2+Vb//EE08wZ84c3n33XXbs2MHUqVO57bbb+O233y46i4jULovxx08YEREHs2vXLiIiIli2bBlXXnklAAMGDKBly5ZYLBaOHDnCL7/8Ur79I488wg8//MCOHTuA0hO8H3jgAR544AGSk5Np3bo1mzdvplu3bkDpyFLTpk1Zvnw5AwcOZMWKFVx55ZUsXbqUq6++GoBZs2Yxffp09uzZQ5s2bQC45557SE5OJiYmhtzcXHx9fVm2bBlRUVHlWe666y7y8vL44osv7HGoRKSaNLIkIg4tPDycfv368dFHHwGwZ88efv/9d+644w7i4+O57LLLKmx/2WWXkZSURElJyUX93C5dupT//wEBAXh6epYXpbLnDh8+DMDOnTvJz89n8ODBNG7cuPzxySefsGfPnovKISK1z8XsACIiF+vOO+9kypQpvP3228yZM4fQ0FCuvvpqDMPAYrFU2LaqwXQnJ6cztikqKqp0W1dX1/L/32KxVPi67DmbzQZQ/n9/+OEHWrZsWWE7d3f3c709ETGZRpZExOHdeOONODs788UXX/Dxxx8zadIkLBYLkZGRrFy5ssK2q1evpn379jg7O5/xOn5+fgCkpaWVP/fHk72rKzIyEnd3dw4cOEDbtm0rPEJCQi769UWkdmlkSUQcXuPGjRk/fjyPP/44WVlZTJw4EYCHHnqISy+9lGeffZbx48ezZs0aZs+ezTvvvFPp6zRq1Ii+ffsya9YswsLCOHr0KE888cRF52vSpAkPP/wwU6dOxWaz0b9/f7Kzs1m9ejWNGzfm9ttvv+ifISK1RyNLIlIv3HnnnZw4cYJBgwbRqlUrAHr06MF//vMf5s2bR6dOnXjqqad45plnystUZT766COKioro1asX999/P88991yN5Hv22Wd56qmnmDlzJhEREQwdOpTvvvuO1q1b18jri0jt0dVwIiIiIlXQyJKIiIhIFVSWRERERKqgsiQiIiJSBZUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKvw/FsCLm/9axZsAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "wg.run()" + "with open(workflow_json_filename) as _f:\n", + " inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\n", + "inputs['a'] = 4.05\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { diff --git a/example_workflows/quantum_espresso/universal_workflow.ipynb b/example_workflows/quantum_espresso/universal_workflow.ipynb index 6e834b7..3b11d4e 100644 --- a/example_workflows/quantum_espresso/universal_workflow.ipynb +++ b/example_workflows/quantum_espresso/universal_workflow.ipynb @@ -23,7 +23,7 @@ "cells": [ { "cell_type": "markdown", - "source": "# Load Quantum Espresso Energy Volume Curve Workflow\n\nThis notebook loads the same, already-exported [`workflow.json`](workflow.json) with six different backends in turn - `aiida-workgraph`, `executorlib`, `jobflow`, `pyiron_base`, `pyiron_workflow` and plain Python - to demonstrate that the PWD format is interoperable regardless of which engine originally produced it.", + "source": "# Load Quantum Espresso Energy Volume Curve Workflow\n\nThis notebook loads the same, already-exported [`workflow.json`](workflow.json) with six different backends in turn - `aiida-core`, `executorlib`, `jobflow`, `pyiron_base`, `pyiron_workflow` and plain Python - to demonstrate that the PWD format is interoperable regardless of which engine originally produced it.", "metadata": {} }, { @@ -60,81 +60,35 @@ }, { "cell_type": "markdown", - "source": "## Aiida\n\n`load_workflow_json` reconstructs the graph as an `aiida-workgraph` `WorkGraph`, after connecting to the local AiiDA profile with `load_profile()`. `wg.run()` executes the relax step followed by the 5 parallel strained SCF calculations and the final plot.", + "source": "## Aiida\n\n`load_workflow_json` reconstructs the graph as an `aiida-core` `GraphHandle`, after connecting to the local AiiDA profile with `load_profile()`. `run_get_node` executes the relax step followed by the 5 parallel strained SCF calculations and the final plot.", "metadata": {} }, { "cell_type": "code", - "source": "from python_workflow_definition.aiida import load_workflow_json\nfrom aiida import load_profile\nload_profile()", - "metadata": { - "trusted": true - }, - "outputs": [ - { - "execution_count": 3, - "output_type": "execute_result", - "data": { - "text/plain": "Profile" - }, - "metadata": {} - } - ], - "execution_count": 3 - }, - { - "cell_type": "code", - "source": "wg = load_workflow_json(file_name='workflow.json')", + "source": "import json\n\nfrom python_workflow_definition.aiida import load_workflow_json\n\nfrom aiida import load_profile\nfrom aiida.engine.launch import run_get_node\n\nload_profile()", "metadata": { "trusted": true }, "outputs": [], - "execution_count": 4 + "execution_count": null }, { "cell_type": "code", - "source": "wg", + "source": "graph = load_workflow_json(file_name='workflow.json')", "metadata": { "trusted": true }, - "outputs": [ - { - "execution_count": 5, - "output_type": "execute_result", - "data": { - "text/plain": "NodeGraphWidget(settings={'minimap': True}, style={'width': '90%', 'height': '600px'}, value={'name': 'WorkGra…", - "application/vnd.jupyter.widget-view+json": { - "version_major": 2, - "version_minor": 1, - "model_id": "ee61bd4c7d90449593a86d50e9eb6acc" - } - }, - "metadata": {} - } - ], - "execution_count": 5 + "outputs": [], + "execution_count": null }, { "cell_type": "code", - "source": "wg.run()\n", + "source": "with open(\"workflow.json\") as _f:\n inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\noutputs, _ = run_get_node(graph, **inputs)\n{k: v.value for k, v in outputs.items()}", "metadata": { "trusted": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": "05/24/2025 06:05:21 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_bulk_structure1\n05/24/2025 06:05:21 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: get_bulk_structure1, type: PyFunction, finished.\n05/24/2025 06:05:21 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict10\n05/24/2025 06:05:22 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: get_dict10, type: PyFunction, finished.\n05/24/2025 06:05:22 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe2\n[jupyter-pythonworkflow-fl--x---d7231032:10748] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\nNote: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n05/24/2025 06:06:12 AM <10674> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['energy', 'volume'])\n05/24/2025 06:06:12 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: calculate_qe2, type: PyFunction, finished.\n05/24/2025 06:06:12 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: generate_structures3\n05/24/2025 06:06:13 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: generate_structures3, type: PyFunction, finished.\n05/24/2025 06:06:13 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict11,get_dict12,get_dict13,get_dict14,get_dict15\n05/24/2025 06:06:14 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: get_dict11, type: PyFunction, finished.\n05/24/2025 06:06:14 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe4,get_dict12,get_dict13,get_dict14,get_dict15\n[jupyter-pythonworkflow-fl--x---d7231032:10760] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\nNote: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n05/24/2025 06:06:24 AM <10674> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n05/24/2025 06:06:24 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: calculate_qe4, type: PyFunction, finished.\n05/24/2025 06:06:24 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict12,get_dict13,get_dict14,get_dict15\n05/24/2025 06:06:25 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: get_dict12, type: PyFunction, finished.\n05/24/2025 06:06:25 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe5,get_dict13,get_dict14,get_dict15\n[jupyter-pythonworkflow-fl--x---d7231032:10770] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\nNote: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n05/24/2025 06:06:36 AM <10674> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n05/24/2025 06:06:36 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: calculate_qe5, type: PyFunction, finished.\n05/24/2025 06:06:36 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict13,get_dict14,get_dict15\n05/24/2025 06:06:37 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: get_dict13, type: PyFunction, finished.\n05/24/2025 06:06:37 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe6,get_dict14,get_dict15\n[jupyter-pythonworkflow-fl--x---d7231032:10781] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\nNote: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n05/24/2025 06:06:49 AM <10674> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n05/24/2025 06:06:49 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: calculate_qe6, type: PyFunction, finished.\n05/24/2025 06:06:49 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict14,get_dict15\n05/24/2025 06:06:50 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: get_dict14, type: PyFunction, finished.\n05/24/2025 06:06:50 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe7,get_dict15\n[jupyter-pythonworkflow-fl--x---d7231032:10792] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\nNote: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n05/24/2025 06:07:05 AM <10674> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n05/24/2025 06:07:06 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: calculate_qe7, type: PyFunction, finished.\n05/24/2025 06:07:06 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_dict15\n05/24/2025 06:07:06 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: get_dict15, type: PyFunction, finished.\n05/24/2025 06:07:06 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: calculate_qe8\n[jupyter-pythonworkflow-fl--x---d7231032:10804] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)\nNote: The following floating-point exceptions are signalling: IEEE_INVALID_FLAG\n05/24/2025 06:07:21 AM <10674> aiida.orm.nodes.process.calculation.calcfunction.CalcFunctionNode: [WARNING] Found extra results that are not included in the output: dict_keys(['structure'])\n05/24/2025 06:07:21 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: calculate_qe8, type: PyFunction, finished.\n05/24/2025 06:07:21 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_list16,get_list17\n05/24/2025 06:07:22 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: get_list16, type: PyFunction, finished.\n05/24/2025 06:07:22 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: get_list17\n05/24/2025 06:07:22 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: get_list17, type: PyFunction, finished.\n05/24/2025 06:07:22 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: plot_energy_volume_curve9\n05/24/2025 06:07:23 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|update_task_state]: Task: plot_energy_volume_curve9, type: PyFunction, finished.\n05/24/2025 06:07:23 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|continue_workgraph]: tasks ready to run: \n05/24/2025 06:07:23 AM <10674> aiida.orm.nodes.process.workflow.workchain.WorkChainNode: [REPORT] [342|WorkGraphEngine|finalize]: Finalize workgraph.\n" - }, - { - "output_type": "display_data", - "data": { - "text/plain": "
", - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAksAAAHACAYAAACyIiyEAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAW/BJREFUeJzt3XlcVXXCx/HPZReUi8omgqC5AO5LKmZp5ZpLWpltmFZOzYwtVlb2tDht2t6UbdOUtttoWrZRmtrkgjuuCLjgwubKIsh6z/MHwkQiKsI9XPi+X6/7eh4u51y+98Rcvv5+5/yOxTAMAxERERGplJPZAURERETqMpUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKqgsmeT555+nX79+eHp64uPjc177GIbBjBkzCAoKolGjRgwcOJAdO3aUfz85ORmLxVLpY/78+We8XkFBAd26dcNisRAXF3dB+d999126dOmCt7c33t7eREVF8dNPP13Qa4iIiDgClSWTFBYWMm7cOP7617+e9z4vvfQSr732GrNnz2b9+vUEBgYyePBgcnJyAAgJCSEtLa3C4x//+AdeXl4MHz78jNd75JFHCAoKqlb+4OBgZs2axYYNG9iwYQNXXXUV1157bYXyJiIiUi8YYqo5c+YYVqv1nNvZbDYjMDDQmDVrVvlz+fn5htVqNd57772z7tetWzfjjjvuOOP5H3/80QgPDzd27NhhAMbmzZsrfH/Hjh3G8OHDDS8vL8Pf39+47bbbjCNHjlSZsWnTpsa///3vc74XERERR6KRJQexb98+0tPTGTJkSPlz7u7uDBgwgNWrV1e6z8aNG4mLi+POO++s8HxGRgaTJ0/m008/xdPT84z90tLSGDBgAN26dWPDhg3ExMSQkZHBjTfeWOnPKSkpYd68eeTm5hIVFXUR71JERKTucTE7gJyf9PR0AAICAio8HxAQwP79+yvd58MPPyQiIoJ+/fqVP2cYBhMnTuSee+6hV69eJCcnn7Hfu+++S48ePXjhhRfKn/voo48ICQkhMTGR9u3bA7Bt2zaioqLIz8+ncePGLFq0iMjIyIt9qyIiInWKRpZq0IwZM856gnXZY8OGDRf1MywWS4WvDcM44zmAU6dO8cUXX5wxqvTWW2+RnZ3N9OnTz/ozNm7cyPLly2ncuHH5Izw8HIA9e/aUb9ehQwfi4uKIjY3lr3/9K7fffjs7d+68mLcnIiJS52hkqQZNmTKFm266qcptwsLCqvXagYGBQOkIU4sWLcqfP3z48BmjTQALFiwgLy+PCRMmVHh+2bJlxMbG4u7uXuH5Xr16ceutt/Lxxx9js9kYNWoUL7744hmv+8ef7ebmRtu2bcv3X79+Pf/85z95//33q/UeRURE6iKVpRrk6+uLr69vrbx269atCQwMZMmSJXTv3h0ovaLut99+q7TUfPjhh4wePRo/P78Kz7/55ps899xz5V+npqYydOhQvvrqK/r06QNAjx49+PrrrwkLC8PF5fx/RQzDoKCgoDpvT0REpM5SWTLJgQMHOH78OAcOHKCkpKR8naO2bdvSuHFjAMLDw5k5cyZjx47FYrHwwAMP8MILL9CuXTvatWvHCy+8gKenJ7fcckuF1969ezf//e9/+fHHH8/4ua1atarwddnPuuSSSwgODgbg73//Ox988AE333wz06ZNw9fXl927dzNv3jw++OADnJ2defzxxxk+fDghISHk5OQwb948VqxYQUxMTE0fKhEREVOpLJnkqaee4uOPPy7/umy0aPny5QwcOBCAhIQEsrKyyrd55JFHOHXqFH/72984ceIEffr04ZdffqFJkyYVXvujjz6iZcuWFa6cuxBBQUGsWrWKRx99lKFDh1JQUEBoaCjDhg3Dyan0NLeMjAyio6NJS0vDarXSpUsXYmJiGDx4cLV+poiISF1lMQzDMDuEiIiISF2lq+FEREREqqCyJCIiIlIFnbNUA2w2G6mpqTRp0qTSNY9ERESk7jEMg5ycHIKCgsrPya2MylINSE1NJSQkxOwYIiIiUg0HDx4svyK8MipLNaDsarSDBw/i7e1tchoRERE5H9nZ2YSEhJxxVfmfqSzVgLKpN29vb5UlERERB3OuU2h0greIiIhIFVSWRERERKqgsiQiIiJSBZUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKqgs1WGGYbBu33HyCovNjiIiItJgqSzVYX/9bBM3vr+GRZtTzI4iIiLSYKks1WG9wpoCMHdVMoZhmJxGRESkYVJZqsNuvDQELzdnkg6fZNXu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Z2z/xhtvYLHo/k4iIiJSkUOs4B0fH09MTAyxsbH06dMHgA8++ICoqCgSEhLo0KHDGfskJSURGxvL9u3b6dixIwDvvPMO/v7+fPnll9x1113l227ZsoXXXnuN9evX06JFC/u8KREREXEIDjGytGbNGqxWa3lRAujbty9Wq5XVq1dXuk9BQQEAHh4e5c85Ozvj5ubGypUry5/Ly8vj5ptvZvbs2QQGBtbSOxARERFH5RBlKT09HX9//zOe9/f3Jz09vdJ9wsPDCQ0NZfr06Zw4cYLCwkJmzZpFeno6aWlp5dtNnTqVfv36ce211553noKCArKzsys8REREpH4ytSzNmDEDi8VS5WPDhg0AlZ5PZBjGWc8zcnV15euvvyYxMZFmzZrh6enJihUrGD58OM7OzgAsXryYZcuW8cYbb1xQ7pkzZ5afaG61WgkJCbmwNy4iIiIOw9RzlqZMmcJNN91U5TZhYWFs3bqVjIyMM7535MgRAgICzrpvz549iYuLIysri8LCQvz8/OjTpw+9evUCYNmyZezZswcfH58K+11//fVcfvnlrFixotLXnT59Og8++GD519nZ2SpMIiIi9ZTFMAzD7BDnEh8fT2RkJGvXrqV3794ArF27lr59+7Jr165KT/CuTFJSEuHh4fz0008MGTKE9PR0jh49WmGbzp07889//pNRo0bRunXr83rd871rsYiIiNQd5/v32yGuhouIiGDYsGFMnjyZ999/H4C//OUvjBw5skJRCg8PZ+bMmYwdOxaA+fPn4+fnR6tWrdi2bRv3338/Y8aMYciQIQAEBgZWelJ3q1atzrsoQel0IKBzl0RERBxI2d/tc40bOURZAvj888+57777yovO6NGjmT17doVtEhISyMrKKv86LS2NBx98kIyMDFq0aMGECRN48sknazxbTk4OgKbiREREHFBOTg5Wq/Ws33eIabi6zmazkZqaSpMmTerEwpZl51AdPHhQ04LoePyZjseZdEwq0vGoSMejovp0PAzDICcnh6CgIJyczn7Nm8OMLNVlTk5OBAcHmx3jDN7e3g7/i1yTdDwq0vE4k45JRToeFel4VFRfjkdVI0plHGKdJRERERGzqCyJiIiIVEFlqR5yd3fn6aefxt3d3ewodYKOR0U6HmfSMalIx6MiHY+KGuLx0AneIiIiIlXQyJKIiIhIFVSWRERERKqgsiQiIiJSBZUlB5aSksJtt91G8+bN8fT0pFu3bmzcuLH8+4ZhMGPGDIKCgmjUqBEDBw5kx44dJiauXVUdj6KiIh599FE6d+6Ml5cXQUFBTJgwgdTUVJNT165z/Y780d13343FYuGNN96wb0g7Op/jER8fz+jRo7FarTRp0oS+ffty4MABkxLXrnMdj5MnTzJlyhSCg4Np1KgRERERvPvuuyYmrj1hYWFYLJYzHn//+9+Bhvd5WtXxaIifpypLDurEiRNcdtlluLq68tNPP7Fz505effVVfHx8yrd56aWXeO2115g9ezbr168nMDCQwYMHl9+epT451/HIy8tj06ZNPPnkk2zatImFCxeSmJjI6NGjzQ1ei87nd6TMN998w9q1awkKCrJ/UDs5n+OxZ88e+vfvT3h4OCtWrGDLli08+eSTeHh4mBe8lpzP8Zg6dSoxMTF89tlnxMfHM3XqVO69916+/fZb84LXkvXr15OWllb+WLJkCQDjxo0DGtbnKVR9PBri5ymGOKRHH33U6N+//1m/b7PZjMDAQGPWrFnlz+Xn5xtWq9V477337BHRrs51PCqzbt06AzD2799fS6nMdb7H5NChQ0bLli2N7du3G6Ghocbrr79e++FMcD7HY/z48cZtt91mp0TmOp/j0bFjR+OZZ56p8FyPHj2MJ554ojaj1Qn333+/cckllxg2m63BfZ5W5o/HozL1/fNUI0sOavHixfTq1Ytx48bh7+9P9+7d+eCDD8q/v2/fPtLT08tvPAyla2MMGDCA1atXmxG5Vp3reFQmKysLi8VS6UhLfXA+x8RmsxEdHc20adPo2LGjSUnt41zHw2az8cMPP9C+fXuGDh2Kv78/ffr04ZtvvjEvdC06n9+P/v37s3jxYlJSUjAMg+XLl5OYmMjQoUNNSm0fhYWFfPbZZ9xxxx1YLJYG93n6Z38+HpWp75+nGllyUO7u7oa7u7sxffp0Y9OmTcZ7771neHh4GB9//LFhGIaxatUqAzBSUlIq7Dd58mRjyJAhZkSuVec6Hn926tQpo2fPnsatt95q56T2cz7H5IUXXjAGDx5c/q/F+jyydK7jkZaWZgCGp6en8dprrxmbN282Zs6caVgsFmPFihUmp6955/P7UVBQYEyYMMEADBcXF8PNzc345JNPTExtH1999ZXh7Oxc/vnZ0D5P/+zPx+PPGsLnqcqSg3J1dTWioqIqPHfvvfcaffv2NQzjf//jTk1NrbDNXXfdZQwdOtRuOe3lXMfjjwoLC41rr73W6N69u5GVlWWviHZ3rmOyYcMGIyAgoMIHYH0uS+c6HikpKQZg3HzzzRW2GTVqlHHTTTfZLae9nM//Zl5++WWjffv2xuLFi40tW7YYb731ltG4cWNjyZIl9o5rV0OGDDFGjhxZ/nVD+zz9sz8fjz9qKJ+nmoZzUC1atCAyMrLCcxEREeVX7QQGBgKQnp5eYZvDhw8TEBBgn5B2dK7jUaaoqIgbb7yRffv2sWTJknpxx+yzOdcx+f333zl8+DCtWrXCxcUFFxcX9u/fz0MPPURYWJgJiWvXuY6Hr68vLi4u5/V7VB+c63icOnWKxx9/nNdee41Ro0bRpUsXpkyZwvjx43nllVfMiGwX+/fvZ+nSpdx1113lzzW0z9M/qux4lGlIn6cqSw7qsssuIyEhocJziYmJhIaGAtC6dWsCAwPLr2CA0nnn3377jX79+tk1qz2c63jA//6HnZSUxNKlS2nevLm9Y9rVuY5JdHQ0W7duJS4urvwRFBTEtGnT+Pnnn82IXKvOdTzc3Ny49NJLz/l7VF+c63gUFRVRVFSEk1PFPxPOzs7YbDa75bS3OXPm4O/vz4gRI8qfa2ifp39U2fGAhvd5qmk4B7Vu3TrDxcXFeP75542kpCTj888/Nzw9PY3PPvusfJtZs2YZVqvVWLhwobFt2zbj5ptvNlq0aGFkZ2ebmLx2nOt4FBUVGaNHjzaCg4ONuLg4Iy0trfxRUFBgcvracT6/I39Wn6fhzud4LFy40HB1dTX+9a9/GUlJScZbb71lODs7G7///ruJyWvH+RyPAQMGGB07djSWL19u7N2715gzZ47h4eFhvPPOOyYmrz0lJSVGq1atjEcfffSM7zWkz9MyZzseDfHzVGXJgX333XdGp06dDHd3dyM8PNz417/+VeH7NpvNePrpp43AwEDD3d3duOKKK4xt27aZlLb2VXU89u3bZwCVPpYvX25e6Fp2rt+RP6vPZckwzu94fPjhh0bbtm0NDw8Po2vXrsY333xjQlL7ONfxSEtLMyZOnGgEBQUZHh4eRocOHYxXX331rJePO7qff/7ZAIyEhIQzvtfQPk8N4+zHoyF+nloMwzDMGNESERERcQQ6Z0lERESkCipLIiIiIlVQWRIRERGpgsqSiIiISBVUlkRERESqoLIkIiIiUgWVJREREZEqqCyJiIiIVEFlSUQatLCwMN544w2zY4hIHaayJCIOa9SoUQwaNKjS761ZswaLxcKmTZvsnEpE6huVJRFxWHfeeSfLli1j//79Z3zvo48+olu3bvTo0cOEZCJSn6gsiYjDGjlyJP7+/sydO7fC83l5eXz11VfceeedfP3113Ts2BF3d3fCwsJ49dVXz/p6ycnJWCwW4uLiyp/LzMzEYrGwYsUKAFasWIHFYuHnn3+me/fuNGrUiKuuuorDhw/z008/ERERgbe3NzfffDN5eXnlr2MYBi+99BJt2rShUaNGdO3alQULFtTk4RCRWqKyJCIOy8XFhQkTJjB37lz+eE/w+fPnU1hYSFRUFDfeeCM33XQT27ZtY8aMGTz55JNnlKvqmDFjBrNnz2b16tUcPHiQG2+8kTfeeIMvvviCH374gSVLlvDWW2+Vb//EE08wZ84c3n33XXbs2MHUqVO57bbb+O233y46i4jULovxx08YEREHs2vXLiIiIli2bBlXXnklAAMGDKBly5ZYLBaOHDnCL7/8Ur79I488wg8//MCOHTuA0hO8H3jgAR544AGSk5Np3bo1mzdvplu3bkDpyFLTpk1Zvnw5AwcOZMWKFVx55ZUsXbqUq6++GoBZs2Yxffp09uzZQ5s2bQC45557SE5OJiYmhtzcXHx9fVm2bBlRUVHlWe666y7y8vL44osv7HGoRKSaNLIkIg4tPDycfv368dFHHwGwZ88efv/9d+644w7i4+O57LLLKmx/2WWXkZSURElJyUX93C5dupT//wEBAXh6epYXpbLnDh8+DMDOnTvJz89n8ODBNG7cuPzxySefsGfPnovKISK1z8XsACIiF+vOO+9kypQpvP3228yZM4fQ0FCuvvpqDMPAYrFU2LaqwXQnJ6cztikqKqp0W1dX1/L/32KxVPi67DmbzQZQ/n9/+OEHWrZsWWE7d3f3c709ETGZRpZExOHdeOONODs788UXX/Dxxx8zadIkLBYLkZGRrFy5ssK2q1evpn379jg7O5/xOn5+fgCkpaWVP/fHk72rKzIyEnd3dw4cOEDbtm0rPEJCQi769UWkdmlkSUQcXuPGjRk/fjyPP/44WVlZTJw4EYCHHnqISy+9lGeffZbx48ezZs0aZs+ezTvvvFPp6zRq1Ii+ffsya9YswsLCOHr0KE888cRF52vSpAkPP/wwU6dOxWaz0b9/f7Kzs1m9ejWNGzfm9ttvv+ifISK1RyNLIlIv3HnnnZw4cYJBgwbRqlUrAHr06MF//vMf5s2bR6dOnXjqqad45plnystUZT766COKioro1asX999/P88991yN5Hv22Wd56qmnmDlzJhEREQwdOpTvvvuO1q1b18jri0jt0dVwIiIiIlXQyJKIiIhIFVSWRERERKqgsiQiIiJSBZUlERERkSqoLImIiIhUQWVJREREpAoqSyIiIiJVUFkSERERqYLKkoiIiEgVVJZEREREqqCyJCIiIlIFlSURERGRKvw/FsCLm/9axZsAAAAASUVORK5CYII=" - }, - "metadata": {} - } - ], - "execution_count": 6 + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", @@ -434,7 +388,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "## Load Workflow with pyiron_workflow\n\nThe graph is loaded into a `pyiron_workflow` `Workflow`, drawn with `.draw()`, and executed with `.run()`." + "source": "## Load Workflow with pyiron_workflow\n\nThe graph is loaded into a `pyiron_workflow` `Workflow`, drawn with `.draw()`, and executed with `run_get_node`." }, { "metadata": {}, diff --git a/example_workflows/quantum_espresso/workflow.py b/example_workflows/quantum_espresso/workflow.py index dcb6a1d..5d6651d 100644 --- a/example_workflows/quantum_espresso/workflow.py +++ b/example_workflows/quantum_espresso/workflow.py @@ -86,3 +86,11 @@ def ase_to_json(atoms): def json_to_ase(atoms_json): structure_restore = StructureResourceAttributes.model_validate_json(atoms_json) return get_ase_atoms(optimade_structure=StructureResource(id="ase", attributes=structure_restore)) + + +def collect_volumes(v0, v1, v2, v3, v4): + return [v0, v1, v2, v3, v4] + + +def collect_energies(v0, v1, v2, v3, v4): + return [v0, v1, v2, v3, v4] diff --git a/pyproject.toml b/pyproject.toml index 812eb59..e064bd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ dynamic = ["version"] [project.optional-dependencies] aiida = [ - "aiida-workgraph>=0.5.1,<=0.9.0", + "aiida-core @ git+https://github.com/aiidateam/aiida-core.git@8c24cbc94dd58f2bb36e70402b51f4124fc90edb", ] jobflow = [ "jobflow>=0.1.18,<=0.3.1", @@ -97,6 +97,9 @@ packages = [ source = "vcs" path = "src/python_workflow_definition/_version.py" +[tool.hatch.metadata] +allow-direct-references = true + [tool.coverage.run] source = ["python_workflow_definition"] command_line = "-m unittest discover tests" diff --git a/src/python_workflow_definition/aiida.py b/src/python_workflow_definition/aiida.py index 9d17dc0..ce9cc7a 100644 --- a/src/python_workflow_definition/aiida.py +++ b/src/python_workflow_definition/aiida.py @@ -1,13 +1,26 @@ -import traceback -from dataclasses import replace -from importlib import import_module -from typing import Any +"""AiiDA graph-authoring adapter: PWD JSON to GraphIR and back. -from aiida import orm -from aiida_pythonjob.data.serializer import general_serializer -from aiida_workgraph import Task, WorkGraph, namespace, task -from aiida_workgraph.socket import TaskSocketNamespace -from node_graph.task_spec import SchemaSource +The adapter speaks the ``@task`` and ``@graph`` language: PWD function nodes +become annotated wrapper tasks around the referenced Python functions, and one +generated ``@graph`` source wires them. Export walks a built ``GraphIR`` back +to PWD nodes and edges. + +Scope: static DAGs plus nested graphs. Branch and loop placements raise +ValueError until PWD grows branch and loop node types. +""" + +from __future__ import annotations + +import importlib +import keyword +import re +import sys +import tempfile +import typing as t +from pathlib import Path + +from aiida.engine.processes.graphs import GraphHandle +from aiida.engine.processes.graphs.ir import GraphIR from python_workflow_definition.models import PythonWorkflowDefinitionWorkflow from python_workflow_definition.shared import ( @@ -19,141 +32,575 @@ TARGET_PORT_LABEL, VERSION_LABEL, VERSION_NUMBER, - convert_nodes_list_to_dict, - set_result_node, - update_node_names, ) +__all__ = ( + "load_workflow_json", + "write_workflow_json", +) -def load_workflow_json(file_name: str) -> WorkGraph: - - data = PythonWorkflowDefinitionWorkflow.load_json_file(file_name=file_name) - - wg = WorkGraph() - task_name_mapping = {} - - nodes_types_dict = {int(n["id"]): n["type"] for n in data[NODES_LABEL]} - for id, identifier in convert_nodes_list_to_dict( - nodes_list=data[NODES_LABEL] - ).items(): - if ( - nodes_types_dict[int(id)] == "function" - and isinstance(identifier, str) - and "." in identifier - ): - p, m = identifier.rsplit(".", 1) - mod = import_module(p) - func = getattr(mod, m) - decorated_func = task(outputs=namespace())(func) - new_task = wg.add_task(decorated_func) - new_task.spec = replace(new_task.spec, schema_source=SchemaSource.EMBEDDED) - task_name_mapping[id] = new_task - else: - # data task - data_node = general_serializer(identifier) - task_name_mapping[id] = data_node - - # add links - for link in data[EDGES_LABEL]: - # TODO: continue here - to_task = task_name_mapping[str(link[TARGET_LABEL])] - # if the input is not exit, it means we pass the data into to the kwargs - # in this case, we add the input socket - if isinstance(to_task, Task): - if link[TARGET_PORT_LABEL] not in to_task.inputs: - to_socket = to_task.add_input_spec( - "workgraph.any", name=link[TARGET_PORT_LABEL] - ) - else: - to_socket = to_task.inputs[link[TARGET_PORT_LABEL]] - from_task = task_name_mapping[str(link[SOURCE_LABEL])] - if isinstance(from_task, orm.Data): - to_socket.value = from_task +# PWD spells whole-value transfer as a null port; GraphIR names it ``result``. +_RESULT_PORT: t.Final = "result" + +# Original module.name per generated task, for round trips. +_ORIGINS: dict[tuple[str, str], str] = {} + +_GENERATED_PREFIX: t.Final = "pwd_generated_graph" + + +def _identifier(name: str, *, what: str) -> str: + if not name.isidentifier(): + msg = f"{what} {name!r} is not a valid Python identifier." + raise ValueError(msg) + return name + + +def _order( + wanted: list[int], + edges: list[dict[str, t.Any]], + node_ids: set[int], +) -> list[int]: + """Order callable nodes so every upstream node comes first. + + :param wanted: ids of ``function`` and ``workflow`` nodes. + :param edges: PWD edge dicts with integer endpoints. + :param node_ids: ids of every node, for validation. + :return: the callable ids in dependency order. + :raises ValueError: on unknown endpoints or dependency cycles. + """ + upstream: dict[int, set[int]] = {node_id: set() for node_id in wanted} + for edge in edges: + source: int = int(edge[SOURCE_LABEL]) + target: int = int(edge[TARGET_LABEL]) + if source not in node_ids or target not in node_ids: + msg = f"Edge references unknown node: {edge}." + raise ValueError(msg) + if target in upstream and source in upstream: + upstream[target].add(source) + ordered: list[int] = [] + ready: list[int] = sorted(node_id for node_id in wanted if not upstream[node_id]) + while ready: + node_id: int = ready.pop(0) + ordered.append(node_id) + for other in wanted: + if node_id in upstream[other]: + upstream[other].discard(node_id) + if not upstream[other] and other not in ordered: + ready.append(other) + ready.sort() + if len(ordered) != len(wanted): + msg = "Callable nodes contain a dependency cycle." + raise ValueError(msg) + return ordered + + +def _expression( + source: int, + port: str | None, + variables: dict[int, str], + inputs: dict[int, str], +) -> str: + """Render the Python expression an edge source maps to. + + :param source: id of the edge source node. + :param port: selected output port, or null for the whole value. + :param variables: node id to assigned variable for callable nodes. + :param inputs: node id to parameter name for input nodes. + :return: an expression selecting the wired value. + """ + if source in inputs: + return inputs[source] + variable: str = variables[source] + if port is None or port == _RESULT_PORT: + return variable + return f"{variable}[{port!r}]" + + +def _port_map( + nodes: dict[int, dict[str, t.Any]], + edges: list[dict[str, t.Any]], + node_id: int, +) -> dict[str, str]: + """Map each wired target port to a valid Python parameter name.""" + mapping: dict[str, str] = {} + ports: list[str] = sorted( + { + str(edge[TARGET_PORT_LABEL]) + for edge in edges + if int(edge[TARGET_LABEL]) == node_id + and edge[TARGET_PORT_LABEL] is not None + } + ) + for port in ports: + if port.isidentifier() and not keyword.iskeyword(port): + safe: str = port else: - try: - if link[SOURCE_PORT_LABEL] is None: - link[SOURCE_PORT_LABEL] = "result" - # because we are not define the outputs explicitly during the pythonjob creation - # we add it here, and assume the output exit - if link[SOURCE_PORT_LABEL] not in from_task.outputs: - # if str(link["sourcePort"]) not in from_task.outputs: - from_socket = from_task.add_output_spec( - "workgraph.any", - name=link[SOURCE_PORT_LABEL], - ) - else: - from_socket = from_task.outputs[link[SOURCE_PORT_LABEL]] - if isinstance(to_task, Task): - wg.add_link(from_socket, to_socket) - except Exception as e: - traceback.print_exc() - print("Failed to link", link, "with error:", e) - return wg - - -def write_workflow_json(wg: WorkGraph, file_name: str) -> None: - data: dict[str, Any] = {NODES_LABEL: [], EDGES_LABEL: []} - node_name_mapping = {} - data_node_name_mapping = {} - i = 0 - GRAPH_LEVEL_NAMES = ["graph_inputs", "graph_outputs", "graph_ctx"] - - for node in wg.tasks: - if node.name in GRAPH_LEVEL_NAMES: + safe = "p_" + re.sub(r"\W", "_", port) + while safe in mapping.values() or keyword.iskeyword(safe): + safe = safe + "_" + mapping[port] = safe + return mapping + + +def _emit_wrappers( + lines: list[str], + nodes: dict[int, dict[str, t.Any]], + edges: list[dict[str, t.Any]], + *, + prefix: str, +) -> None: + """Emit imports plus ``@task`` wrappers for the function nodes.""" + for node_id in sorted(nodes): + node: dict[str, t.Any] = nodes[node_id] + if node["type"] != "function": continue + value: str = node["value"] + module_path, _, func_name = value.rpartition(".") + if not module_path: + msg = f"Function node {node_id} value is not a module path: {value!r}." + raise ValueError(msg) + used: list[str] = sorted( + { + str(edge[SOURCE_PORT_LABEL]) + for edge in edges + if int(edge[SOURCE_LABEL]) == node_id + and edge[SOURCE_PORT_LABEL] not in (None, _RESULT_PORT) + } + ) + lines.append(f"from {module_path} import {func_name} as _orig_{node_id}") + ports: dict[str, str] = _port_map(nodes, edges, node_id) + task_params: str = ", ".join(f"{safe}: Any" for _, safe in ports.items()) + if used: + lines.append(f"class {prefix}_Out{node_id}(t.TypedDict):") + for port in used: + lines.append(f" {port}: Any") + lines.append("") + lines.append("@task") + if used: + lines.append( + f"def {prefix}_task_{node_id}(*, {task_params}) -> {prefix}_Out{node_id}:" + ) + else: + lines.append(f"def {prefix}_task_{node_id}(*, {task_params}) -> Any:") + plain: list[str] = [ + safe + "=" + safe for orig, safe in ports.items() if safe == orig + ] + packed: str = ", ".join( + repr(orig) + ": " + safe for orig, safe in ports.items() if safe != orig + ) + call: str = ", ".join(plain) + if packed: + call = (call + ", " if call else "") + "**{" + packed + "}" + lines.append(" return _orig_" + str(node_id) + "(" + call + ")") + lines.append("") + - node_name_mapping[node.name] = i - - executor = node.get_executor() - callable_name = f"{executor.module_path}.{executor.callable_name}" - - data[NODES_LABEL].append({"id": i, "type": "function", "value": callable_name}) - i += 1 - - for link in wg.links: - link_data = link.to_dict() - # if the from socket is the default result, we set it to None - if link_data["from_socket"] == "result": - link_data["from_socket"] = None - link_data[TARGET_LABEL] = node_name_mapping[link_data.pop("to_task")] - link_data[TARGET_PORT_LABEL] = link_data.pop("to_socket") - link_data[SOURCE_LABEL] = node_name_mapping[link_data.pop("from_task")] - link_data[SOURCE_PORT_LABEL] = link_data.pop("from_socket") - data[EDGES_LABEL].append(link_data) - - for node in wg.tasks: - for input in node.inputs: - # assume namespace is not used as input - if isinstance(input, TaskSocketNamespace): +def _emit_graph_function( # noqa: PLR0913 + lines: list[str], + nodes: dict[int, dict[str, t.Any]], + edges: list[dict[str, t.Any]], + *, + name: str, + params: list[str], + inputs: dict[int, str], + outputs: dict[str, tuple[int, str | None]], + prefix: str, +) -> None: + """Append one ``@graph`` function definition wiring the given nodes.""" + callable_ids: list[int] = [ + node_id + for node_id, node in nodes.items() + if node["type"] in ("function", "workflow") + ] + order: list[int] = _order(callable_ids, edges, set(nodes.keys())) + variables: dict[int, str] = {} + renames: dict[int, dict[str, str]] = { + node_id: _port_map(nodes, edges, node_id) for node_id in callable_ids + } + lines.append("@graph") + lines.append("def " + name + "(" + ", ".join(params) + "):") + for node_id in order: + node: dict[str, t.Any] = nodes[node_id] + variables[node_id] = "v" + str(node_id) + if node["type"] == "function": + callee: str = prefix + "_task_" + str(node_id) + else: + callee = prefix + "_graph_" + str(node_id) + arguments: list[str] = [] + for edge in edges: + if int(edge[TARGET_LABEL]) != node_id: continue - if isinstance(input.value, orm.Data): - if input.value.uuid not in data_node_name_mapping: - if isinstance(input.value, orm.List): - raw_value = input.value.get_list() - elif isinstance(input.value, orm.Dict): - raw_value = input.value.get_dict() - # unknow reason, there is a key "node_type" in the dict - raw_value.pop("node_type", None) - else: - raw_value = input.value.value - data[NODES_LABEL].append( - {"id": i, "type": "input", "value": raw_value} - ) - input_node_name = i - data_node_name_mapping[input.value.uuid] = input_node_name - i += 1 - else: - input_node_name = data_node_name_mapping[input.value.uuid] - data[EDGES_LABEL].append( + expression: str = _expression( + int(edge[SOURCE_LABEL]), + edge[SOURCE_PORT_LABEL], + variables, + inputs, + ) + arguments.append( + renames[node_id][str(edge[TARGET_PORT_LABEL])] + "=" + expression + ) + lines.append( + " v" + str(node_id) + " = " + callee + "(" + ", ".join(arguments) + ")" + ) + returned: list[str] = [] + for output_name, (source_id, source_port) in outputs.items(): + expression = _expression(source_id, source_port, variables, inputs) + returned.append(repr(output_name) + ": " + expression) + lines.append(" return {" + ", ".join(returned) + "}") + lines.append("") + + +def _emit_level( + lines: list[str], + nodes: dict[int, dict[str, t.Any]], + edges: list[dict[str, t.Any]], + *, + graph_name: str, + prefix: str, +) -> None: + """Emit wrapper tasks, nested graphs, and one ``@graph`` definition.""" + inputs: dict[int, str] = {} + params: list[str] = [] + taken: set[str] = set() + for node_id in sorted(nodes): + node: dict[str, t.Any] = nodes[node_id] + if node["type"] == "input": + param: str = _identifier(node["name"], what="Input") + # Distinct nodes may share a name with different defaults. + # Keep the first name, suffix later ones deterministically. + base: str = param + counter: int = 2 + while param in taken: + param = base + "_" + str(counter) + counter += 1 + taken.add(param) + inputs[node_id] = param + params.append(param + "=" + repr(node["value"])) + _emit_wrappers(lines, nodes, edges, prefix=prefix) + for node_id in sorted(nodes): + node = nodes[node_id] + if node["type"] != "workflow": + continue + template: dict[str, t.Any] = node.get("graph", {}) + inner_nodes: dict[int, dict[str, t.Any]] = { + int(item["id"]): item for item in template.get(NODES_LABEL, []) + } + inner_edges: list[dict[str, t.Any]] = [ + { + TARGET_LABEL: int(item[TARGET_LABEL]), + TARGET_PORT_LABEL: item[TARGET_PORT_LABEL], + SOURCE_LABEL: int(item[SOURCE_LABEL]), + SOURCE_PORT_LABEL: item[SOURCE_PORT_LABEL], + } + for item in template.get(EDGES_LABEL, []) + ] + _emit_level( + lines, + inner_nodes, + inner_edges, + graph_name=prefix + "_graph_" + str(node_id), + prefix=prefix + "_g" + str(node_id), + ) + outputs: dict[str, tuple[int, str | None]] = {} + for node_id in sorted(nodes): + node = nodes[node_id] + if node["type"] != "output": + continue + output_name: str = _identifier(node["name"], what="Output") + for edge in edges: + if int(edge[TARGET_LABEL]) == node_id: + outputs[output_name] = ( + int(edge[SOURCE_LABEL]), + edge[SOURCE_PORT_LABEL], + ) + _emit_graph_function( + lines, + nodes, + edges, + name=graph_name, + params=params, + inputs=inputs, + outputs=outputs, + prefix=prefix, + ) + + +def _pwd_to_graph_source( + workflow: dict[str, t.Any], + *, + module_name: str = _GENERATED_PREFIX, +) -> str: + """Render ``@task`` and ``@graph`` source for one PWD workflow dict. + + Function nodes become annotated wrapper tasks around the referenced + functions: a synthesized ``TypedDict`` declares the output ports named by + downstream edges, otherwise the wrapper returns ``Any``. Nested + ``workflow`` nodes become nested ``@graph`` definitions. + + :param workflow: validated PWD mapping with ``nodes`` and ``edges``. + :param module_name: name carried in the module docstring only. + :return: importable Python source using the ``@task``/``@graph`` language. + :raises ValueError: on unwritable PWD content. + """ + nodes: dict[int, dict[str, t.Any]] = { + int(node["id"]): node for node in workflow[NODES_LABEL] + } + edges: list[dict[str, t.Any]] = [ + { + TARGET_LABEL: int(edge[TARGET_LABEL]), + TARGET_PORT_LABEL: edge[TARGET_PORT_LABEL], + SOURCE_LABEL: int(edge[SOURCE_LABEL]), + SOURCE_PORT_LABEL: edge[SOURCE_PORT_LABEL], + } + for edge in workflow[EDGES_LABEL] + ] + for node in nodes.values(): + if node["type"] not in ("function", "input", "output", "workflow"): + msg = ( + "Node " + + str(node["id"]) + + " of type " + + repr(node["type"]) + + " has no PWD representation in this adapter." + ) + raise ValueError(msg) + lines: list[str] = [ + '"""Generated from PWD (' + module_name + '); do not edit by hand."""', + "from __future__ import annotations", + "", + "import typing as t", + "from typing import Any", + "", + "from aiida.engine import graph, task", + "", + ] + _emit_level(lines, nodes, edges, graph_name="graph_main", prefix="pwd") + return "\n".join(lines) + "\n" + + +def load_workflow_json( + file_name: str | Path, + *, + module_name: str = _GENERATED_PREFIX, + source_file: str | Path | None = None, +) -> GraphHandle: + """Build a ``GraphHandle`` from one PWD JSON file. + + The PWD workflow renders to ``@task`` and ``@graph`` source, which is + imported so the graph language lowers it. The generated module name is + recorded with the original ``module.name`` of every task for round trips + through :func:`write_workflow_json`. + + :param file_name: path of the PWD JSON file. + :param module_name: name of the generated module. + :param source_file: optional path persisting the generated source; + otherwise a temporary file is used. + :return: the imported graph handle; call ``.build()`` for the template. + :raises ValueError: on unwritable PWD content. + """ + data: dict[str, t.Any] = PythonWorkflowDefinitionWorkflow.load_json_file( + file_name=file_name + ) + source: str = _pwd_to_graph_source(data, module_name=module_name) + if source_file is None: + directory: str = tempfile.mkdtemp(prefix="pwd_graph_") + module_path: Path = Path(directory) / (module_name + ".py") + module_path.write_text(source, encoding="utf-8") + else: + module_path = Path(source_file) + module_path.write_text(source, encoding="utf-8") + directory = str(module_path.parent) + if directory not in sys.path: + sys.path.insert(0, directory) + module = importlib.import_module(module_name) + handle: GraphHandle = module.graph_main + for node in data[NODES_LABEL]: + if node["type"] == "function": + _ORIGINS[(module_name, "pwd_task_" + str(node["id"]))] = node["value"] + return handle + + +def _placement_value(module_name: str, task_name: str, spec: dict[str, t.Any]) -> str: + """Resolve the PWD function value for one placement. + + :param module_name: module of the lowered graph template. + :param task_name: placement name in the template. + :param spec: task spec with ``identifier`` and ``executor`` entries. + :return: the original ``module.name`` when recorded, else the spec one. + """ + executor: dict[str, t.Any] = spec.get("executor", {}) + return _ORIGINS.get( + (executor.get("module", module_name), task_name), + str(executor.get("module")) + "." + str(executor.get("name")), + ) + + +def _emit_template( + template: dict[str, t.Any], + module_name: str, + nodes: list[dict[str, t.Any]], + edges: list[dict[str, t.Any]], + counter: list[int], +) -> None: + """Append PWD nodes and edges for one GraphIR template level. + + :param template: ``to_dict`` mapping of a graph template. + :param module_name: module recorded for origin lookups. + :param nodes: PWD nodes to extend. + :param edges: PWD edges to extend. + :param counter: single running node id counter. + :raises ValueError: on branch or loop placements. + """ + placement_ids: dict[str, int] = {} + for placement in template.get("tasks", []): + kind: str = placement.get("kind", "process") + if kind not in ("process", "graph"): + msg = ( + "Placement " + + str(placement.get("name")) + + " of kind " + + repr(kind) + + " has no PWD representation yet; PWD covers static DAGs plus nested graphs." + ) + raise ValueError(msg) + placement_ids[placement["name"]] = counter[0] + counter[0] += 1 + name_to_id: dict[str, int] = dict(placement_ids) + input_ids: dict[str, int] = {} + fields: dict[str, dict[str, t.Any]] = template.get("input_namespace", {}).get( + "fields", {} + ) + for input_name in template.get("inputs", {}): + input_ids[input_name] = counter[0] + default: t.Any = fields.get(input_name, {}).get("default") + nodes.append( + {"id": counter[0], "type": "input", "value": default, "name": input_name} + ) + counter[0] += 1 + for placement in template.get("tasks", []): + node_id: int = name_to_id[placement["name"]] + if placement["kind"] == "process": + value: str = _placement_value( + module_name, placement["name"], placement.get("spec", {}) + ) + nodes.append({"id": node_id, "type": "function", "value": value}) + for port_name, port_value in placement.get("inputs", {}).items(): + value_id: int = counter[0] + nodes.append({"id": value_id, "type": "input", "value": port_value}) + edges.append( { - TARGET_LABEL: node_name_mapping[node.name], - TARGET_PORT_LABEL: input._name, - SOURCE_LABEL: input_node_name, + TARGET_LABEL: node_id, + TARGET_PORT_LABEL: port_name, + SOURCE_LABEL: value_id, SOURCE_PORT_LABEL: None, } ) + counter[0] += 1 + else: + body: dict[str, t.Any] = placement.get("body", {}) + inner_nodes: list[dict[str, t.Any]] = [] + inner_edges: list[dict[str, t.Any]] = [] + inner_counter: list[int] = [0] + _emit_template(body, module_name, inner_nodes, inner_edges, inner_counter) + graph_body: dict[str, t.Any] = { + NODES_LABEL: inner_nodes, + EDGES_LABEL: inner_edges, + "inputs": list(body.get("inputs", {}).keys()), + "outputs": list(body.get("outputs", {}).keys()), + } + nodes.append({"id": node_id, "type": "workflow", "graph": graph_body}) + for dependency in template.get("dependencies", []): + source_port: str | None = dependency.get("source_port") + edges.append( + { + TARGET_LABEL: name_to_id[dependency["target"]], + TARGET_PORT_LABEL: dependency.get("target_port"), + SOURCE_LABEL: name_to_id[dependency["source"]], + SOURCE_PORT_LABEL: ( + None if source_port == _RESULT_PORT else source_port + ), + } + ) + for input_name, routes in template.get("inputs", {}).items(): + for route in routes: + edges.append( + { + TARGET_LABEL: name_to_id[route[0]], + TARGET_PORT_LABEL: route[1], + SOURCE_LABEL: input_ids[input_name], + SOURCE_PORT_LABEL: None, + } + ) + for output_name, endpoint in template.get("outputs", {}).items(): + output_id: int = counter[0] + nodes.append({"id": output_id, "type": "output", "name": output_name}) + endpoint_port: str | None = endpoint.get("port") + edges.append( + { + TARGET_LABEL: output_id, + TARGET_PORT_LABEL: None, + SOURCE_LABEL: name_to_id[endpoint["task"]], + SOURCE_PORT_LABEL: ( + None if endpoint_port == _RESULT_PORT else endpoint_port + ), + } + ) + counter[0] += 1 + - data[VERSION_LABEL] = VERSION_NUMBER - PythonWorkflowDefinitionWorkflow( - **set_result_node(workflow_dict=update_node_names(workflow_dict=data)) - ).dump_json_file(file_name=file_name, indent=2) +def write_workflow_json( + graph: GraphHandle | GraphIR, + file_name: str | Path, + inputs: dict[str, t.Any] | None = None, +) -> None: + """Write one built graph to a PWD JSON file. + + Static DAGs plus nested graphs round-trip; branch and loop placements + raise ValueError until PWD grows those node types. ``inputs`` sets values + for template inputs that have no literal default, such as computed paths. + + :param graph: a ``GraphHandle`` (built on the spot) or a ``GraphIR``. + :param file_name: path of the PWD JSON file to write. + :param inputs: input name to value, applied over template defaults. + :raises ValueError: on unknown input names. + """ + template: GraphIR = graph.build() if isinstance(graph, GraphHandle) else graph + payload: dict[str, t.Any] = template.to_dict() + nodes: list[dict[str, t.Any]] = [] + edges: list[dict[str, t.Any]] = [] + _emit_template(payload, template.identifier or "", nodes, edges, [0]) + counts: dict[str, int] = {} + for node in nodes: + if node["type"] != "input" or "name" in node: + continue + ports: list[str] = sorted( + { + str(edge[TARGET_PORT_LABEL]) + for edge in edges + if edge[SOURCE_LABEL] == node["id"] + and edge[TARGET_PORT_LABEL] is not None + } + ) + port: str = ports[0] if ports else "value" + counts[port] = counts.get(port, 0) + 1 + node["name"] = port + "_" + str(counts[port] - 1) + for node in nodes: + if node["type"] == "input" and inputs and node["name"] in inputs: + node["value"] = inputs[node["name"]] + if inputs: + unknown: list[str] = sorted( + name + for name in inputs + if name not in {node["name"] for node in nodes if node["type"] == "input"} + ) + if unknown: + msg = f"Unknown graph inputs: {', '.join(unknown)}." + raise ValueError(msg) + data: dict[str, t.Any] = { + NODES_LABEL: nodes, + EDGES_LABEL: edges, + VERSION_LABEL: VERSION_NUMBER, + } + PythonWorkflowDefinitionWorkflow(**data).dump_json_file( + file_name=file_name, indent=2 + ) diff --git a/tests/test_aiida.py b/tests/test_aiida.py index 1b71b94..febd58a 100644 --- a/tests/test_aiida.py +++ b/tests/test_aiida.py @@ -1,44 +1,127 @@ +import json +import sys +import tempfile import unittest -import os -from aiida_workgraph import WorkGraph, task -from aiida import orm, load_profile -load_profile() +from pathlib import Path -from python_workflow_definition.aiida import load_workflow_json, write_workflow_json +from pydantic import ValidationError +from python_workflow_definition.aiida import ( + load_workflow_json, + write_workflow_json, +) -def get_prod_and_div(x, y): - return {"prod": x * y, "div": x / y} +EXAMPLES = Path(__file__).resolve().parent.parent / "example_workflows" +sys.path.insert(0, str(EXAMPLES / "arithmetic")) -def get_sum(x, y): - return x + y +class TestAiiDAGraphs(unittest.TestCase): + def test_arithmetic_round_trip(self): + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + source = EXAMPLES / "arithmetic" / "workflow.json" + handle = load_workflow_json(source, module_name="pwd_test_arith") + template = handle.build().to_dict() + self.assertEqual( + sorted(task["name"] for task in template["tasks"]), + ["pwd_task_0", "pwd_task_1", "pwd_task_2"], + ) + exported = tmp_path / "arithmetic.json" + write_workflow_json(handle, exported) + values = sorted( + node["value"] + for node in json.loads(exported.read_text())["nodes"] + if node["type"] == "function" + ) + self.assertEqual( + values, + [ + "workflow.get_prod_and_div", + "workflow.get_square", + "workflow.get_sum", + ], + ) + inputs = sorted( + (node["name"], node["value"]) + for node in json.loads(exported.read_text())["nodes"] + if node["type"] == "input" + ) + self.assertEqual(inputs, [("x", 1), ("y", 2)]) + round_tripped = ( + load_workflow_json(exported, module_name="pwd_test_arith_rt") + .build() + .to_dict() + ) + self.assertEqual( + sorted(task["name"] for task in round_tripped["tasks"]), + ["pwd_task_0", "pwd_task_1", "pwd_task_2"], + ) + def test_workflow_node_rejected(self): + nested = { + "version": "0.1.1", + "nodes": [ + { + "id": 0, + "type": "workflow", + "graph": {"nodes": [], "edges": []}, + }, + ], + "edges": [], + } + with tempfile.TemporaryDirectory() as tmp: + nested_file = Path(tmp) / "nested.json" + nested_file.write_text(json.dumps(nested)) + with self.assertRaisesRegex(ValidationError, "workflow"): + load_workflow_json(nested_file, module_name="pwd_test_nested") -def get_square(x): - return x ** 2 + def test_duplicate_input_names_suffixed(self): + duplicate = { + "version": "0.1.0", + "nodes": [ + {"id": 0, "type": "input", "name": "x", "value": 1}, + {"id": 1, "type": "input", "name": "x", "value": 2}, + { + "id": 2, + "type": "function", + "value": "python_workflow_definition.shared.get_dict", + }, + {"id": 3, "type": "output", "name": "result"}, + ], + "edges": [ + { + "target": 2, + "targetPort": "a", + "source": 0, + "sourcePort": None, + }, + { + "target": 2, + "targetPort": "b", + "source": 1, + "sourcePort": None, + }, + { + "target": 3, + "targetPort": None, + "source": 2, + "sourcePort": None, + }, + ], + } + with tempfile.TemporaryDirectory() as tmp: + duplicate_file = Path(tmp) / "duplicate.json" + duplicate_file.write_text(json.dumps(duplicate)) + tasks = ( + load_workflow_json(duplicate_file, module_name="pwd_test_dup") + .build() + .to_dict()["tasks"] + ) + self.assertEqual( + sorted(task["name"] for task in tasks), + ["pwd_task_2"], + ) -class TestAiiDA(unittest.TestCase): - def test_aiida(self): - workflow_json_filename = "aiida_simple.json" - wg = WorkGraph("arithmetic") - get_prod_and_div_task = wg.add_task( - task(outputs=['prod', 'div'])(get_prod_and_div), - x=orm.Float(1), - y=orm.Float(2), - ) - get_sum_task = wg.add_task( - get_sum, - x=get_prod_and_div_task.outputs.prod, - y=get_prod_and_div_task.outputs.div, - ) - get_square_task = wg.add_task( - get_square, - x=get_sum_task.outputs.result, - ) - write_workflow_json(wg=wg, file_name=workflow_json_filename) - workgraph = load_workflow_json(file_name=workflow_json_filename) - workgraph.run() - - self.assertTrue(os.path.exists(workflow_json_filename)) \ No newline at end of file +if __name__ == "__main__": + unittest.main()