diff --git a/binder/environment.yml b/binder/environment.yml index fc162be..149a6fd 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 - conda_subprocess =0.0.12 - networkx =3.6.1 - cwltool =3.2.20260413085819 +- pip +- pip: + - git+https://github.com/aiidateam/aiida-core.git@main diff --git a/documentation/intro.md b/documentation/intro.md index 4ad9228..27a00cc 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 graphs](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..f6dd598 100644 --- a/example_workflows/arithmetic/aiida.ipynb +++ b/example_workflows/arithmetic/aiida.ipynb @@ -23,12 +23,20 @@ "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", + "\n", + "This notebook defines the arithmetic workflow with the aiida-core graph authoring layer (`@task` and `@graph`) 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", + "Tasks are plain Python functions annotated with `@task`; the `@graph` function wires their calls without executing them. Building the graph needs no profile; only execution does." + ], "metadata": {} }, { @@ -36,87 +44,102 @@ "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 because they are still plain Python functions at this point; the wrappers below turn each one into an AiiDA task with declared outputs. `get_prod_and_div` returns a dictionary, so a `TypedDict` declares `prod` and `div` as its output ports. Setting `__module__` keeps the exported `module.function` values pointing at `workflow.py`." + ], "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\n", + "def 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` builds the `GraphIR` and serializes it into the PWD `workflow.json` format: each task becomes a `function` node, each dependency becomes an edge, and the graph defaults become `input` values." + ], "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", @@ -158,12 +181,14 @@ }, { "cell_type": "code", - "source": "flow = load_workflow_json(file_name=workflow_json_filename)", + "source": [ + "flow = load_workflow_json(file_name=workflow_json_filename)" + ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 11 + "execution_count": null }, { "cell_type": "code", @@ -204,7 +229,9 @@ }, { "cell_type": "code", - "source": "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()", + "source": [ + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" + ], "metadata": { "trusted": true }, @@ -260,7 +287,9 @@ "cell_type": "code", "outputs": [], "execution_count": null, - "source": "wf = load_workflow_json(file_name=workflow_json_filename)" + "source": [ + "wf = load_workflow_json(file_name=workflow_json_filename)" + ] }, { "metadata": {}, diff --git a/example_workflows/arithmetic/executorlib.ipynb b/example_workflows/arithmetic/executorlib.ipynb index 612c615..c5332bd 100644 --- a/example_workflows/arithmetic/executorlib.ipynb +++ b/example_workflows/arithmetic/executorlib.ipynb @@ -4,7 +4,9 @@ "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,28 +161,23 @@ "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()" ] }, @@ -196,64 +193,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(None) 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()}" ] }, { @@ -284,7 +242,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "4b45e83b-945f-48c5-8e20-9df0ce0a14a1", "metadata": {}, "outputs": [], @@ -453,8 +411,7 @@ } ], "source": [ - "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\n", - "delayed_object_lst[-1].draw()" + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" ] }, { @@ -505,7 +462,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "5c759f6d-d82d-44f8-8120-e7732988fe71", "metadata": {}, "outputs": [], diff --git a/example_workflows/arithmetic/jobflow.ipynb b/example_workflows/arithmetic/jobflow.ipynb index 3ff3b1c..bbbe591 100644 --- a/example_workflows/arithmetic/jobflow.ipynb +++ b/example_workflows/arithmetic/jobflow.ipynb @@ -30,7 +30,7 @@ { "id": "e6180712-d081-45c7-ba41-fc5191f10427", "cell_type": "markdown", - "source": "## Define workflow with jobflow\n\nThis tutorial will demonstrate how to use the PWD with `jobflow` and load the workflow with `aiida` and `pyiron`.\n\n[`jobflow`](https://joss.theoj.org/papers/10.21105/joss.05995) was developed to simplify the development of high-throughput workflows. It uses a decorator-based approach to define the “Job“s that can be connected to form complex workflows (“Flow“s). `jobflow` is the workflow language of the workflow library [`atomate2`](https://chemrxiv.org/engage/chemrxiv/article-details/678e76a16dde43c9085c75e9), designed to replace [atomate](https://www.sciencedirect.com/science/article/pii/S0927025617303919), which was central to the development of the [Materials Project](https://pubs.aip.org/aip/apm/article/1/1/011002/119685/Commentary-The-Materials-Project-A-materials) database.", + "source": "## Define workflow with jobflow\n\nThis tutorial will demonstrate how to use the PWD with `jobflow` and load the workflow with `aiida` and `pyiron`.\n\n[`jobflow`](https://joss.theoj.org/papers/10.21105/joss.05995) was developed to simplify the development of high-throughput workflows. It uses a decorator-based approach to define the \u201cJob\u201cs that can be connected to form complex workflows (\u201cFlow\u201cs). `jobflow` is the workflow language of the workflow library [`atomate2`](https://chemrxiv.org/engage/chemrxiv/article-details/678e76a16dde43c9085c75e9), designed to replace [atomate](https://www.sciencedirect.com/science/article/pii/S0927025617303919), which was central to the development of the [Materials Project](https://pubs.aip.org/aip/apm/article/1/1/011002/119685/Commentary-The-Materials-Project-A-materials) database.", "metadata": {} }, { @@ -81,7 +81,7 @@ { "id": "2639deadfae9c591", "cell_type": "markdown", - "source": "Using the job object decorator, the imported functions from the arithmetic workflow are transformed into jobflow “Job”s. These “Job”s can delay the execution of Python functions and can be chained into workflows (“Flow”s). A “Job” can return serializable outputs (e.g., a number, a dictionary, or a Pydantic model) or a so-called “Response” object, which enables the execution of dynamic workflows where the number of nodes is not known prior to the workflow’s execution. ", + "source": "Using the job object decorator, the imported functions from the arithmetic workflow are transformed into jobflow \u201cJob\u201ds. These \u201cJob\u201ds can delay the execution of Python functions and can be chained into workflows (\u201cFlow\u201ds). A \u201cJob\u201d can return serializable outputs (e.g., a number, a dictionary, or a Pydantic model) or a so-called \u201cResponse\u201d object, which enables the execution of dynamic workflows where the number of nodes is not known prior to the workflow\u2019s execution. ", "metadata": { "collapsed": false, "jupyter": { @@ -210,21 +210,20 @@ { "id": "66a1b3a6-3d3b-4caa-b58f-d8bc089b1074", "cell_type": "code", - "source": "from aiida import load_profile\n\nload_profile()", + "source": [ + "import json\n", + "\n", + "from aiida import load_profile\n", + "\n", + "from aiida.engine.launch import run_get_node\n", + "\n", + "load_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", @@ -250,26 +249,14 @@ { "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 +272,17 @@ { "id": "05228ece-643c-420c-8df8-4ce3df379515", "cell_type": "code", - "source": "wg.run()", + "source": [ + "with open(None) 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()}" + ], "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", @@ -317,7 +303,9 @@ { "id": "8f2a621d-b533-4ddd-8bcd-c22db2f922ec", "cell_type": "code", - "source": "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()", + "source": [ + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" + ], "metadata": { "trusted": true }, @@ -387,7 +375,9 @@ "cell_type": "code", "outputs": [], "execution_count": null, - "source": "wf = load_workflow_json(file_name=workflow_json_filename)", + "source": [ + "wf = load_workflow_json(file_name=workflow_json_filename)" + ], "id": "33b1e9408924f9c0" }, { @@ -407,4 +397,4 @@ "id": "bf110395454bd694" } ] -} +} \ No newline at end of file diff --git a/example_workflows/arithmetic/pyiron_base.ipynb b/example_workflows/arithmetic/pyiron_base.ipynb index eaba0a5..c8e3ef0 100644 --- a/example_workflows/arithmetic/pyiron_base.ipynb +++ b/example_workflows/arithmetic/pyiron_base.ipynb @@ -24,7 +24,9 @@ { "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 +150,28 @@ { "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", + "\n", + "from aiida import load_profile\n", + "\n", + "from aiida.engine.launch import run_get_node\n", + "\n", + "load_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", @@ -183,42 +186,29 @@ { "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(None) 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()}" + ], "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", @@ -249,12 +239,14 @@ { "id": "4b45e83b-945f-48c5-8e20-9df0ce0a14a1", "cell_type": "code", - "source": "flow = load_workflow_json(file_name=workflow_json_filename)", + "source": [ + "flow = load_workflow_json(file_name=workflow_json_filename)" + ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 17 + "execution_count": null }, { "id": "8665c39c-220c-4982-b738-c31f6460530f", @@ -305,7 +297,9 @@ "cell_type": "code", "outputs": [], "execution_count": null, - "source": "wf = load_workflow_json(file_name=workflow_json_filename)", + "source": [ + "wf = load_workflow_json(file_name=workflow_json_filename)" + ], "id": "5b442611457aa5a8" }, { diff --git a/example_workflows/arithmetic/pyiron_workflow.ipynb b/example_workflows/arithmetic/pyiron_workflow.ipynb index 491322e..60557e6 100644 --- a/example_workflows/arithmetic/pyiron_workflow.ipynb +++ b/example_workflows/arithmetic/pyiron_workflow.ipynb @@ -4,7 +4,9 @@ "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 +764,9 @@ "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 +775,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 +801,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 +811,10 @@ "metadata": {}, "outputs": [], "source": [ - "wg.run()" + "with open(None) 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()}" ] }, { @@ -877,8 +887,7 @@ "metadata": {}, "outputs": [], "source": [ - "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\n", - "delayed_object_lst[-1].draw()" + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" ] }, { @@ -913,4 +922,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/example_workflows/arithmetic/universal_workflow.ipynb b/example_workflows/arithmetic/universal_workflow.ipynb index a100ed2..3ba926a 100644 --- a/example_workflows/arithmetic/universal_workflow.ipynb +++ b/example_workflows/arithmetic/universal_workflow.ipynb @@ -23,12 +23,12 @@ "cells": [ { "cell_type": "markdown", - "source": "# Load Simple Workflow\n\nThis notebook loads a single, pre-existing `workflow.json` with several different engines in turn. Since the JSON only describes function nodes, inputs, outputs and the edges between them, it does not matter which engine originally wrote it — any of the engines below can load and execute it.", + "source": "# Load Simple Workflow\n\nThis notebook loads a single, pre-existing `workflow.json` with several different engines in turn. Since the JSON only describes function nodes, inputs, outputs and the edges between them, it does not matter which engine originally wrote it \u2014 any of the engines below can load and execute it.", "metadata": {} }, { "cell_type": "markdown", - "source": "## Plot\n\nBefore running the workflow with any engine, `python_workflow_definition.plot.plot` renders `workflow.json` directly as a graph of function, input and output nodes — independent of any particular workflow engine.", + "source": "## Plot\n\nBefore running the workflow with any engine, `python_workflow_definition.plot.plot` renders `workflow.json` directly as a graph of function, input and output nodes \u2014 independent of any particular workflow engine.", "metadata": {} }, { @@ -60,73 +60,55 @@ }, { "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", + "\n", + "Loading 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`, feeding the `input` node values as launch inputs." + ], "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": {} - } + "source": [ + "import json\n", + "\n", + "from python_workflow_definition.aiida import load_workflow_json\n", + "\n", + "from aiida import load_profile\n", + "from aiida.engine.launch import run_get_node\n", + "\n", + "load_profile()" ], - "execution_count": 3 - }, - { - "cell_type": "code", - "source": "workgraph = 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": [ + "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\"}\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "outputs[\"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", @@ -203,12 +185,14 @@ }, { "cell_type": "code", - "source": "flow = load_workflow_json(file_name=\"workflow.json\")", + "source": [ + "flow = load_workflow_json(file_name=\"workflow.json\")" + ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 13 + "execution_count": null }, { "cell_type": "code", @@ -249,7 +233,9 @@ }, { "cell_type": "code", - "source": "delayed_object_lst = load_workflow_json(file_name=\"workflow.json\")\ndelayed_object_lst[-1].draw()", + "source": [ + "delayed_object_lst = load_workflow_json(file_name=\"workflow.json\")\ndelayed_object_lst[-1].draw()" + ], "metadata": { "trusted": true }, @@ -305,7 +291,9 @@ "cell_type": "code", "outputs": [], "execution_count": null, - "source": "wf = load_workflow_json(file_name=\"workflow.json\")" + "source": [ + "wf = load_workflow_json(file_name=\"workflow.json\")" + ] }, { "metadata": {}, diff --git a/example_workflows/nfdi/aiida.ipynb b/example_workflows/nfdi/aiida.ipynb index ef72f1b..72ef399 100644 --- a/example_workflows/nfdi/aiida.ipynb +++ b/example_workflows/nfdi/aiida.ipynb @@ -24,13 +24,23 @@ { "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", + "\n", + "https://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n", + "\n", + "This notebook defines the NFDI4Ing file-based workflow benchmark with the aiida-core graph authoring layer (`@task` and `@graph`) 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 \u2014 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", + "Tasks are plain Python functions annotated with `@task` and wired in a `@graph` function without executing anything, so building the graph needs no AiiDA profile. `generate_mesh`, `plot_over_line`, `substitute_macros` and `compile_paper` each return a single value; `convert_to_xdmf` and `poisson` return a `dict`, so a `TypedDict` declares each dictionary key as its own output port that later tasks can connect to." + ], "metadata": {} }, { @@ -46,36 +56,104 @@ { "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", + "\n", + "from 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\n", + "from typing import Any\n", + "\n", + "from 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", + "\n", + "class XdmfOut(t.TypedDict):\n", + " xdmf_file: Any\n", + " h5_file: Any\n", + "\n", + "\n", + "class PoissonOut(t.TypedDict):\n", + " numdofs: Any\n", + " pvd_file: Any\n", + " vtu_file: Any\n", + "\n", + "\n", + "def generate_mesh(domain_size, source_directory) -> Any:\n", + " return _generate_mesh(domain_size=domain_size, source_directory=source_directory)\n", + "\n", + "\n", + "generate_mesh.__module__ = \"workflow\"\n", + "generate_mesh = task(generate_mesh)\n", + "\n", + "\n", + "def convert_to_xdmf(gmsh_output_file) -> XdmfOut:\n", + " return _convert_to_xdmf(gmsh_output_file)\n", + "\n", + "\n", + "convert_to_xdmf.__module__ = \"workflow\"\n", + "convert_to_xdmf = task(convert_to_xdmf)\n", + "\n", + "\n", + "def poisson(meshio_output_xdmf, meshio_output_h5, source_directory) -> PoissonOut:\n", + " return _poisson(meshio_output_xdmf, meshio_output_h5, source_directory)\n", + "\n", + "\n", + "poisson.__module__ = \"workflow\"\n", + "poisson = task(poisson)\n", + "\n", + "\n", + "def 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", + "\n", + "plot_over_line.__module__ = \"workflow\"\n", + "plot_over_line = task(plot_over_line)\n", + "\n", + "\n", + "def 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", + "\n", + "substitute_macros.__module__ = \"workflow\"\n", + "substitute_macros = task(substitute_macros)\n", + "\n", + "\n", + "def compile_paper(macros_tex, plot_file, source_directory) -> Any:\n", + " return _compile_paper(macros_tex, plot_file, source_directory)\n", + "\n", + "\n", + "compile_paper.__module__ = \"workflow\"\n", + "compile_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)", + "cell_type": "markdown", + "source": [ + "The wrappers above turn each plain function into an AiiDA task with declared outputs. Setting `__module__` keeps the exported `module.function` values pointing at `workflow.py`." + ], "metadata": { "trusted": true }, @@ -85,138 +163,68 @@ { "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.", + "source": [ + "Inputs are plain JSON-compatible values. They become PWD `input` nodes with defaults, so downstream engines receive concrete values." + ], "metadata": {} }, { "id": "37c9d988-1755-446c-9f7b-c32f99e280d4", "cell_type": "code", - "source": "source_directory = orm.Str(os.path.abspath(os.path.join(os.curdir, \"source\")))", + "source": [ + "source_directory = os.path.abspath(os.path.join(os.curdir, \"source\"))" + ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 5 + "execution_count": null }, { "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\")", + "source": [ + "@graph\n", + "def 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": 7 + "execution_count": null }, { "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.", - "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", - "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)", - "metadata": { - "trusted": true - }, - "outputs": [], - "execution_count": 13 - }, - { - "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": {} - } + "source": [ + "Each task call above 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 with `[\"key\"]` selection; 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." ], - "execution_count": 14 + "metadata": {} }, { "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.", + "source": [ + "Built `GraphIR` in hand, `write_workflow_json` serializes it into the PWD `workflow.json` format \u2014 one `function` node per task, `input` nodes for `domain_size`/`source_directory` with their defaults, and edges recording which task output feeds which task\u2019s input port, as shown by the `cat` output below." + ], "metadata": {} }, { @@ -232,12 +240,18 @@ { "id": "4139547a-9717-4708-b1a5-8202a0382d77", "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": { "trusted": true }, "outputs": [], - "execution_count": 16 + "execution_count": null }, { "id": "490df6a3-e402-4620-acf2-dbcfa0c5d537", @@ -258,7 +272,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` \u2014 no reference to the aiida-core graph 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": {} }, { @@ -284,12 +298,14 @@ { "id": "c3528d55-6bf7-47ed-a110-65c47446ba92", "cell_type": "code", - "source": "flow = load_workflow_json(file_name=workflow_json_filename)", + "source": [ + "flow = load_workflow_json(file_name=workflow_json_filename)" + ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 20 + "execution_count": null }, { "id": "5ef48778-4209-425f-8c6b-85a4cd2f92ec", @@ -334,7 +350,9 @@ { "id": "4452a648-d4c3-4f5f-8678-ffb54bef17dc", "cell_type": "code", - "source": "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()", + "source": [ + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" + ], "metadata": { "trusted": true }, @@ -393,7 +411,9 @@ "cell_type": "code", "outputs": [], "execution_count": null, - "source": "wf = load_workflow_json(file_name=workflow_json_filename)", + "source": [ + "wf = load_workflow_json(file_name=workflow_json_filename)" + ], "id": "668fe64b18b4d604" }, { diff --git a/example_workflows/nfdi/executorlib.ipynb b/example_workflows/nfdi/executorlib.ipynb index 8a2e0ba..1ffea13 100644 --- a/example_workflows/nfdi/executorlib.ipynb +++ b/example_workflows/nfdi/executorlib.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "id": "106ded66-d202-46ac-82b0-2755ca309bdd", "metadata": {}, - "source": "# executorlib\n\nhttps://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n\nThis notebook defines the NFDI4Ing file-based workflow benchmark with [`executorlib`](https://github.com/pyiron/executorlib) and then loads the resulting `workflow.json` into `aiida`, `jobflow`, `pyiron_base` and `pyiron_workflow`. Every stage of the pipeline shells out to 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)." + "source": "# executorlib\n\nhttps://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n\nThis notebook defines the NFDI4Ing file-based workflow benchmark with [`executorlib`](https://github.com/pyiron/executorlib) and then loads the resulting `workflow.json` into `aiida`, `jobflow`, `pyiron_base` and `pyiron_workflow`. Every stage of the pipeline shells out to 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 \u2014 see [`workflow.py`](workflow.py)." }, { "cell_type": "markdown", @@ -75,7 +75,7 @@ { "cell_type": "markdown", "id": "57d670cb", - "source": "Passing `export_workflow_filename` to `SingleNodeExecutor` makes it record every `submit` call as a side effect and write them out as a PWD `workflow.json` once the `with` block exits — no separate `write_workflow_json` call is needed. The six `submit` calls below mirror the six pipeline stages: mesh generation, XDMF conversion, the Poisson solve, line-plot postprocessing, LaTeX macro substitution and paper compilation.", + "source": "Passing `export_workflow_filename` to `SingleNodeExecutor` makes it record every `submit` call as a side effect and write them out as a PWD `workflow.json` once the `with` block exits \u2014 no separate `write_workflow_json` call is needed. The six `submit` calls below mirror the six pipeline stages: mesh generation, XDMF conversion, the Poisson solve, line-plot postprocessing, LaTeX macro substitution and paper compilation.", "metadata": {} }, { @@ -301,28 +301,23 @@ "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()" ] }, @@ -338,72 +333,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(None) 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()}" ] }, { @@ -434,7 +382,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "6e7f3614-c971-4e2d-83f0-96f0d0fc04de", "metadata": {}, "outputs": [], @@ -771,8 +719,7 @@ } ], "source": [ - "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\n", - "delayed_object_lst[-1].draw()" + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" ] }, { @@ -840,7 +787,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "id": "50055483ca2c9909", "metadata": {}, "outputs": [], diff --git a/example_workflows/nfdi/jobflow.ipynb b/example_workflows/nfdi/jobflow.ipynb index 77c5041..23bcbda 100644 --- a/example_workflows/nfdi/jobflow.ipynb +++ b/example_workflows/nfdi/jobflow.ipynb @@ -30,7 +30,7 @@ { "id": "106ded66-d202-46ac-82b0-2755ca309bdd", "cell_type": "markdown", - "source": "https://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n\nThis notebook defines the NFDI4Ing file-based workflow benchmark with [`jobflow`](https://github.com/materialsproject/jobflow) and then loads the resulting `workflow.json` into `aiida`, `pyiron_base` and `pyiron_workflow`. Every stage of the pipeline shells out to 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).", + "source": "https://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n\nThis notebook defines the NFDI4Ing file-based workflow benchmark with [`jobflow`](https://github.com/materialsproject/jobflow) and then loads the resulting `workflow.json` into `aiida`, `pyiron_base` and `pyiron_workflow`. Every stage of the pipeline shells out to 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 \u2014 see [`workflow.py`](workflow.py).", "metadata": {} }, { @@ -209,27 +209,28 @@ { "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", + "\n", + "from aiida import load_profile\n", + "\n", + "from aiida.engine.launch import run_get_node\n", + "\n", + "load_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", @@ -244,42 +245,29 @@ { "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(None) 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()}" + ], "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", @@ -300,7 +288,9 @@ { "id": "4452a648-d4c3-4f5f-8678-ffb54bef17dc", "cell_type": "code", - "source": "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()", + "source": [ + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" + ], "metadata": { "trusted": true }, @@ -359,7 +349,9 @@ "cell_type": "code", "outputs": [], "execution_count": null, - "source": "wf = load_workflow_json(file_name=workflow_json_filename)", + "source": [ + "wf = load_workflow_json(file_name=workflow_json_filename)" + ], "id": "728b8684fee475b5" }, { diff --git a/example_workflows/nfdi/pyiron_base.ipynb b/example_workflows/nfdi/pyiron_base.ipynb index 164b5af..4593835 100644 --- a/example_workflows/nfdi/pyiron_base.ipynb +++ b/example_workflows/nfdi/pyiron_base.ipynb @@ -24,7 +24,7 @@ { "id": "106ded66-d202-46ac-82b0-2755ca309bdd", "cell_type": "markdown", - "source": "# pyiron\n\nhttps://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n\nThis notebook defines the NFDI4Ing file-based workflow benchmark with [`pyiron_base`](https://github.com/pyiron/pyiron_base) and then loads the resulting `workflow.json` into `aiida`, `jobflow` and `pyiron_workflow`. Every stage of the pipeline shells out to 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).", + "source": "# pyiron\n\nhttps://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n\nThis notebook defines the NFDI4Ing file-based workflow benchmark with [`pyiron_base`](https://github.com/pyiron/pyiron_base) and then loads the resulting `workflow.json` into `aiida`, `jobflow` and `pyiron_workflow`. Every stage of the pipeline shells out to 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 \u2014 see [`workflow.py`](workflow.py).", "metadata": {} }, { @@ -167,7 +167,9 @@ { "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`) \u2014 it walks the graph backwards to discover every upstream job." + ], "metadata": {} }, { @@ -193,27 +195,28 @@ { "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", + "\n", + "from aiida import load_profile\n", + "\n", + "from aiida.engine.launch import run_get_node\n", + "\n", + "load_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", @@ -228,42 +231,29 @@ { "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(None) 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()}" + ], "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", @@ -294,12 +284,14 @@ { "id": "6e7f3614-c971-4e2d-83f0-96f0d0fc04de", "cell_type": "code", - "source": "flow = load_workflow_json(file_name=workflow_json_filename)", + "source": [ + "flow = load_workflow_json(file_name=workflow_json_filename)" + ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 21 + "execution_count": null }, { "id": "2d87ed45-f5d9-403f-a03a-26be4a47a3ef", @@ -344,7 +336,9 @@ "cell_type": "code", "outputs": [], "execution_count": null, - "source": "wf = load_workflow_json(file_name=workflow_json_filename)", + "source": [ + "wf = load_workflow_json(file_name=workflow_json_filename)" + ], "id": "50055483ca2c9909" }, { diff --git a/example_workflows/nfdi/pyiron_workflow.ipynb b/example_workflows/nfdi/pyiron_workflow.ipynb index 2bc76e3..1953e4d 100644 --- a/example_workflows/nfdi/pyiron_workflow.ipynb +++ b/example_workflows/nfdi/pyiron_workflow.ipynb @@ -24,7 +24,7 @@ { "id": "106ded66-d202-46ac-82b0-2755ca309bdd", "cell_type": "markdown", - "source": "# pyiron\n\nhttps://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n\nThis notebook defines the NFDI4Ing file-based workflow benchmark with [`pyiron_workflow`](https://github.com/pyiron/pyiron_workflow) and then loads the resulting `workflow.json` into `aiida`, `jobflow` and `pyiron_base`. Every stage of the pipeline shells out to 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).", + "source": "# pyiron\n\nhttps://github.com/BAMresearch/NFDI4IngScientificWorkflowRequirements\n\nThis notebook defines the NFDI4Ing file-based workflow benchmark with [`pyiron_workflow`](https://github.com/pyiron/pyiron_workflow) and then loads the resulting `workflow.json` into `aiida`, `jobflow` and `pyiron_base`. Every stage of the pipeline shells out to 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 \u2014 see [`workflow.py`](workflow.py).", "metadata": {} }, { @@ -223,27 +223,28 @@ { "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", + "\n", + "from aiida import load_profile\n", + "\n", + "from aiida.engine.launch import run_get_node\n", + "\n", + "load_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", @@ -258,42 +259,29 @@ { "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(None) 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()}" + ], "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", @@ -324,12 +312,14 @@ { "id": "6e7f3614-c971-4e2d-83f0-96f0d0fc04de", "cell_type": "code", - "source": "flow = load_workflow_json(file_name=workflow_json_filename)", + "source": [ + "flow = load_workflow_json(file_name=workflow_json_filename)" + ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 23 + "execution_count": null }, { "id": "2d87ed45-f5d9-403f-a03a-26be4a47a3ef", @@ -374,7 +364,9 @@ { "id": "6aeba274-10d3-4e79-8ee4-4d870f163939", "cell_type": "code", - "source": "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()", + "source": [ + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" + ], "metadata": { "trusted": true }, @@ -425,4 +417,4 @@ "execution_count": null } ] -} +} \ No newline at end of file diff --git a/example_workflows/nfdi/universal_workflow.ipynb b/example_workflows/nfdi/universal_workflow.ipynb index 8ceeaad..57586a6 100644 --- a/example_workflows/nfdi/universal_workflow.ipynb +++ b/example_workflows/nfdi/universal_workflow.ipynb @@ -23,12 +23,12 @@ "cells": [ { "cell_type": "markdown", - "source": "# Load Simple Workflow\n\nThis notebook loads the single, pre-existing NFDI4Ing `workflow.json` — the file-based benchmark from [`workflow.py`](workflow.py) that runs `gmsh`, `meshio`, a FEniCS-based Poisson solver, ParaView's `pvbatch` and `tectonic` in turn — with several different engines. Since the JSON only describes function nodes, inputs, outputs and the edges between them, it does not matter which engine originally wrote it; any of the engines below can load and execute it, each re-running the same chain of external tools.", + "source": "# Load Simple Workflow\n\nThis notebook loads the single, pre-existing NFDI4Ing `workflow.json` \u2014 the file-based benchmark from [`workflow.py`](workflow.py) that runs `gmsh`, `meshio`, a FEniCS-based Poisson solver, ParaView's `pvbatch` and `tectonic` in turn \u2014 with several different engines. Since the JSON only describes function nodes, inputs, outputs and the edges between them, it does not matter which engine originally wrote it; any of the engines below can load and execute it, each re-running the same chain of external tools.", "metadata": {} }, { "cell_type": "markdown", - "source": "## Plot\n\nBefore running the workflow with any engine, `python_workflow_definition.plot.plot` renders `workflow.json` directly as a graph of function, input and output nodes — independent of any particular workflow engine.", + "source": "## Plot\n\nBefore running the workflow with any engine, `python_workflow_definition.plot.plot` renders `workflow.json` directly as a graph of function, input and output nodes \u2014 independent of any particular workflow engine.", "metadata": {} }, { @@ -60,73 +60,66 @@ }, { "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", + "\n", + "Loading 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", + "\n", + "from python_workflow_definition.aiida import load_workflow_json\n", + "\n", + "from aiida import load_profile\n", + "from aiida.engine.launch import run_get_node\n", + "\n", + "load_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\"}\n", + "outputs, _ = 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", @@ -203,12 +196,14 @@ }, { "cell_type": "code", - "source": "flow = load_workflow_json(file_name=\"workflow.json\")", + "source": [ + "flow = load_workflow_json(file_name=\"workflow.json\")" + ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 13 + "execution_count": null }, { "cell_type": "code", @@ -249,7 +244,9 @@ }, { "cell_type": "code", - "source": "delayed_object_lst = load_workflow_json(file_name=\"workflow.json\")\ndelayed_object_lst[-1].draw()", + "source": [ + "delayed_object_lst = load_workflow_json(file_name=\"workflow.json\")\ndelayed_object_lst[-1].draw()" + ], "metadata": { "trusted": true }, @@ -305,7 +302,9 @@ "cell_type": "code", "outputs": [], "execution_count": null, - "source": "wf = load_workflow_json(file_name=\"workflow.json\")" + "source": [ + "wf = load_workflow_json(file_name=\"workflow.json\")" + ] }, { "metadata": {}, @@ -324,7 +323,7 @@ { "metadata": {}, "cell_type": "markdown", - "source": "## Python\n\nAs a baseline, `python_workflow_definition.purepython.load_workflow_json` interprets the JSON directly in plain Python, with no workflow engine involved: it evaluates each function node in dependency order — invoking `gmsh`, `meshio`, the Poisson solver, `pvbatch` and `tectonic` via `conda_subprocess` exactly as `workflow.py` defines — and returns the final result." + "source": "## Python\n\nAs a baseline, `python_workflow_definition.purepython.load_workflow_json` interprets the JSON directly in plain Python, with no workflow engine involved: it evaluates each function node in dependency order \u2014 invoking `gmsh`, `meshio`, the Poisson solver, `pvbatch` and `tectonic` via `conda_subprocess` exactly as `workflow.py` defines \u2014 and returns the final result." }, { "cell_type": "code", diff --git a/example_workflows/quantum_espresso/aiida.ipynb b/example_workflows/quantum_espresso/aiida.ipynb index 8b297f2..7d9b6b4 100644 --- a/example_workflows/quantum_espresso/aiida.ipynb +++ b/example_workflows/quantum_espresso/aiida.ipynb @@ -3,297 +3,126 @@ { "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\n", + "\n", + "This notebook builds the Quantum Espresso energy-volume-curve workflow directly with the aiida-core graph authoring layer (`@task` and `@graph`) 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", + "Tasks are plain Python functions annotated with `@task` and wired in a `@graph` function without executing anything, so building the graph needs no AiiDA profile. `calculate_qe` returns a dict, so a `TypedDict` exposes `energy`, `volume` and `structure` as separate output ports; `generate_structures` returns one entry per strain (`s_0` ... `s_4`), exposed the same way. The five SCF branches are spelled out explicitly: each strains structure gets its own `calculate_qe` node, and two `collect_five` tasks gather the energies and volumes into lists for the final plot." + ] }, { "cell_type": "code", - "execution_count": 1, - "metadata": { - "trusted": true - }, + "metadata": {}, + "execution_count": null, "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, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "from aiida_workgraph import task, WorkGraph" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "trusted": true - }, + "metadata": {}, + "execution_count": null, "outputs": [], "source": [ "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", ")" ] }, - { - "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.", - "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])" - ] - }, - { - "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 - }, + "execution_count": null, "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", + "def collect_volumes(v0, v1, v2, v3, v4) -> Any:\n", + " return _collect_volumes(v0, v1, v2, v3, v4)\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", - ")" + "\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)" ] }, { "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.", - "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": [], + "metadata": {}, "source": [ - "get_energies_task = wg.add_task(get_list)" + "The wrappers above turn each plain function into an AiiDA task with declared outputs and point `__module__` at the module the other engines import from. The graph below spells out the five SCF branches explicitly, one `calculate_qe` node per strained structure." ] }, { "cell_type": "code", + "metadata": {}, "execution_count": null, - "metadata": { - "trusted": true - }, - "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" - } - ], + "metadata": {}, "source": [ - "wg" + "Graph inputs that are plain values become PWD `input` nodes with defaults; `write_workflow_json` below serializes the built `GraphIR` into the PWD `workflow.json` format so it can be loaded by another engine." ] }, { "cell_type": "code", - "execution_count": 15, - "metadata": { - "trusted": true - }, + "metadata": {}, + "execution_count": null, "outputs": [], "source": [ - "_ = write_workflow_json(wg=wg, file_name=workflow_json_filename)\n" + "write_workflow_json(qe_eos, file_name=workflow_json_filename)" ] }, { "cell_type": "code", + "metadata": {}, "execution_count": 16, - "metadata": { - "trusted": true - }, "outputs": [ { "name": "stdout", @@ -894,7 +723,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": { "trusted": true }, @@ -1850,8 +1679,7 @@ } ], "source": [ - "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\n", - "delayed_object_lst[-1].draw()" + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" ] }, { @@ -2012,7 +1840,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": { "trusted": true }, diff --git a/example_workflows/quantum_espresso/executorlib.ipynb b/example_workflows/quantum_espresso/executorlib.ipynb index e4ebd6f..eaf5616 100644 --- a/example_workflows/quantum_espresso/executorlib.ipynb +++ b/example_workflows/quantum_espresso/executorlib.ipynb @@ -4,7 +4,9 @@ "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,27 +692,22 @@ "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()" ] @@ -727,101 +724,25 @@ }, { "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)" ] }, { "cell_type": "code", "execution_count": null, - "id": "d81a4fb5-a674-41a9-ac19-24c56f952468", - "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" - } - ], + "outputs": [], "source": [ - "wg.run()" + "with open(file_name=workflow_json_filename) as _f:\n", + " inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\ninputs['a'] = 4.05\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { @@ -852,7 +773,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "48a27146-7372-40ab-8b02-e2a9283d4748", "metadata": {}, "outputs": [], @@ -1708,8 +1629,7 @@ } ], "source": [ - "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\n", - "delayed_object_lst[-1].draw()" + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" ] }, { @@ -1784,7 +1704,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "id": "97ef39ae859855ab", "metadata": {}, "outputs": [], diff --git a/example_workflows/quantum_espresso/jobflow.ipynb b/example_workflows/quantum_espresso/jobflow.ipynb index d8b1e1f..1cecc0a 100644 --- a/example_workflows/quantum_espresso/jobflow.ipynb +++ b/example_workflows/quantum_espresso/jobflow.ipynb @@ -71,7 +71,7 @@ }, "source": [ "## Quantum Espresso Workflow\n", - "We will use the knowledge from the previous arithmetic workflow example to create the Quantum Espresso-related tasks for calculating an \"Energy vs. Volume\" curve. It’s important to note that this is only a basic implementation, and further extensions towards data validation or for a simplified user experience can be added. For example, one can typically configure run commands for quantum-chemical programs via configuration files in atomate2." + "We will use the knowledge from the previous arithmetic workflow example to create the Quantum Espresso-related tasks for calculating an \"Energy vs. Volume\" curve. It\u2019s important to note that this is only a basic implementation, and further extensions towards data validation or for a simplified user experience can be added. For example, one can typically configure run commands for quantum-chemical programs via configuration files in atomate2." ] }, { @@ -857,24 +857,17 @@ }, { "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()" ] @@ -892,134 +885,27 @@ }, { "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)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "scrolled": true, "trusted": true }, "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\"}\ninputs['a'] = 4.05\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { @@ -1771,8 +1657,7 @@ } ], "source": [ - "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\n", - "delayed_object_lst[-1].draw()" + "delayed_object_lst = load_workflow_json(file_name=workflow_json_filename)\ndelayed_object_lst[-1].draw()" ] }, { @@ -1933,7 +1818,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": { "trusted": true }, diff --git a/example_workflows/quantum_espresso/pyiron_base.ipynb b/example_workflows/quantum_espresso/pyiron_base.ipynb index 1369193..d0938a3 100644 --- a/example_workflows/quantum_espresso/pyiron_base.ipynb +++ b/example_workflows/quantum_espresso/pyiron_base.ipynb @@ -4,7 +4,9 @@ "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,29 +789,24 @@ "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()" ] @@ -828,109 +825,29 @@ }, { "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)" ] }, { "cell_type": "code", "execution_count": null, - "id": "d81a4fb5-a674-41a9-ac19-24c56f952468", - "metadata": { - "trusted": true - }, - "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" - } - ], + "outputs": [], "source": [ - "wg.run()" + "with open(file_name=workflow_json_filename) as _f:\n", + " inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\ninputs['a'] = 4.05\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { @@ -965,7 +882,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "48a27146-7372-40ab-8b02-e2a9283d4748", "metadata": { "trusted": true @@ -1189,7 +1106,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "id": "97ef39ae859855ab", "metadata": { "trusted": true diff --git a/example_workflows/quantum_espresso/pyiron_workflow.ipynb b/example_workflows/quantum_espresso/pyiron_workflow.ipynb index 88d94a2..1164668 100644 --- a/example_workflows/quantum_espresso/pyiron_workflow.ipynb +++ b/example_workflows/quantum_espresso/pyiron_workflow.ipynb @@ -4,7 +4,9 @@ "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,29 +4723,24 @@ "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()" ] @@ -4762,109 +4759,29 @@ }, { "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)" ] }, { "cell_type": "code", "execution_count": null, - "id": "539e0245-c31c-4fb6-badd-c6ef155b43fa", - "metadata": { - "trusted": true - }, - "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" - } - ], + "outputs": [], "source": [ - "wg.run()" + "with open(file_name=workflow_json_filename) as _f:\n", + " inputs = {n[\"name\"]: n[\"value\"] for n in json.load(_f)[\"nodes\"] if n[\"type\"] == \"input\"}\ninputs['a'] = 4.05\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "{k: v.value for k, v in outputs.items()}" ] }, { @@ -4899,7 +4816,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "id": "9b03e3e7-3dba-4640-9869-76916413bfc0", "metadata": { "trusted": true @@ -5861,8 +5778,7 @@ } ], "source": [ - "delayed_object_lst = load_workflow_json(file_name=\"workflow.json\")\n", - "delayed_object_lst[-1].draw()" + "delayed_object_lst = load_workflow_json(file_name=\"workflow.json\")\ndelayed_object_lst[-1].draw()" ] }, { @@ -6039,4 +5955,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/example_workflows/quantum_espresso/universal_workflow.ipynb b/example_workflows/quantum_espresso/universal_workflow.ipynb index 6e834b7..8b6b1b2 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 graphs`, `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,55 @@ }, { "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, feeding the `input` node values as launch inputs." + ], "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": {} - } + "source": [ + "import json\n", + "\n", + "from python_workflow_definition.aiida import load_workflow_json\n", + "\n", + "from aiida import load_profile\n", + "from aiida.engine.launch import run_get_node\n", + "\n", + "load_profile()" ], - "execution_count": 3 - }, - { - "cell_type": "code", - "source": "wg = load_workflow_json(file_name='workflow.json')", "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\"}\n", + "outputs, _ = run_get_node(graph, **inputs)\n", + "outputs[\"result\"]" + ], "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+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=" - }, - "metadata": {} - } - ], - "execution_count": 6 + "outputs": [], + "execution_count": null }, { "cell_type": "markdown", @@ -208,12 +182,14 @@ }, { "cell_type": "code", - "source": "flow = load_workflow_json(file_name=\"workflow.json\")", + "source": [ + "flow = load_workflow_json(file_name=\"workflow.json\")" + ], "metadata": { "trusted": true }, "outputs": [], - "execution_count": 13 + "execution_count": null }, { "cell_type": "code", @@ -332,7 +308,9 @@ }, { "cell_type": "code", - "source": "delayed_object_lst = load_workflow_json(file_name=\"workflow.json\")\ndelayed_object_lst[-1].draw()", + "source": [ + "delayed_object_lst = load_workflow_json(file_name=\"workflow.json\")\ndelayed_object_lst[-1].draw()" + ], "metadata": { "trusted": true }, @@ -448,7 +426,9 @@ "cell_type": "code", "outputs": [], "execution_count": null, - "source": "wf = load_workflow_json(file_name=\"workflow.json\")" + "source": [ + "wf = load_workflow_json(file_name=\"workflow.json\")" + ] }, { "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..3dbc9ef 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@main", ] jobflow = [ "jobflow>=0.1.18,<=0.3.1", @@ -93,6 +93,9 @@ packages = [ "src/python_workflow_definition" ] +[tool.hatch.metadata] +allow-direct-references = true + [tool.hatch.version] source = "vcs" path = "src/python_workflow_definition/_version.py" diff --git a/src/python_workflow_definition/aiida.py b/src/python_workflow_definition/aiida.py index 9d17dc0..f498001 100644 --- a/src/python_workflow_definition/aiida.py +++ b/src/python_workflow_definition/aiida.py @@ -1,13 +1,24 @@ -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 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 +30,545 @@ 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", +) + +# 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. -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 + :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 _task_params( + nodes: dict[int, dict[str, t.Any]], + edges: list[dict[str, t.Any]], + node_id: int, +) -> list[str]: + """Collect the input port names wired into one callable node, in order.""" + return 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 + } + ) + + +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}") + task_params: str = ", ".join( + f"{port}: Any" for port in _task_params(nodes, edges, node_id) + ) + 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: - # 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 + lines.append(f"def {prefix}_task_{node_id}(*, {task_params}) -> Any:") + ports = _task_params(nodes, edges, node_id) + joined = ", ".join(port + "=" + port for port in ports) + lines.append(" return _orig_" + str(node_id) + "(" + joined + ")") + lines.append("") + + +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] = {} + 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: - 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: + callee = prefix + "_graph_" + str(node_id) + arguments: list[str] = [] + for edge in edges: + if int(edge[TARGET_LABEL]) != node_id: + continue + expression: str = _expression( + int(edge[SOURCE_LABEL]), + edge[SOURCE_PORT_LABEL], + variables, + inputs, + ) + arguments.append(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] = [] + 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") + 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, + ) - 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): - 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( + +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 + + +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. - 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) + 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..d893383 100644 --- a/tests/test_aiida.py +++ b/tests/test_aiida.py @@ -1,44 +1,80 @@ +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 - -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()