Note
Go to the end to download the full example code.
Using the Context
Introduction
In AiiDA workflows (both, traditional WorkChain
s, as well as the WorkGraph
), the Context (typically
represented by ctx
), is an internal container that can hold data shared between different tasks.
It’s particularly useful for more complex workflows.
When to use
Many-to-many relationships where direct linking becomes unwieldy
Conditional workflows where links depend on runtime conditions
Graph builders that need to expose selective internal state
#
Passing data to the context
There are three ways to set data in the Context:
- Setting the ctx
attribute of the WorkGraph directly
- Using the update_ctx
method of the WorkGraph
- Using the workgraph.set_context
task
So let’s have a look how to use each of them:
from aiida_workgraph import WorkGraph, task
from aiida import load_profile
from aiida.orm import Int
load_profile()
wg1 = WorkGraph(name="context_1")
# 1. Setting the ``ctx`` attribute of the WorkGraph directly, on initialization
wg1.ctx = {"x": Int(2), "data.y": Int(3)}
# 2. Using the ``update_ctx`` method
wg2 = WorkGraph(name="context_2")
@task.calcfunction()
def add(x, y):
return x + y
add1 = wg2.add_task(add, "add1", x=2, y=3)
# set result of add1 to ctx.sum
wg2.update_ctx({"sum": add1.outputs.result})
# 3. Using the ``workgraph.set_context`` task to set either a task result (socket) or a resolved value to the ctx
wg3 = WorkGraph(name="context_3")
add1 = wg3.add_task(add, "add1", x=2, y=3)
wg3.add_task(
"workgraph.set_context", name="set_ctx1", key="sum", value=add1.outputs.result
)
SetContext(name='set_ctx1', properties=[], inputs=['context', 'key', 'value', '_wait'], outputs=['_wait'])
Nested context keys
To organize the context data in a hierarchical structure, the keys may contain dots .`
that create nesting
Here is an example, to group the results of multipl add tasks to ctx.sum:
wg = WorkGraph(name="ctx_nested")
add1 = wg.add_task(add, "add1", x=1, y=2)
add2 = wg.add_task(add, "add2", x=3, y=4)
wg.update_ctx({"sum.add1": add1.outputs.result})
wg.update_ctx({"sum.add2": add2.outputs.result})
# Or, alternatively:
# wg.update_ctx({
# "sum": {
# "add1": add1.outputs.result,
# "add2": add2.outputs.result
# }
# })
print(wg.ctx.sum)
TaskSocketNamespace(name='sum', sockets=['add1', 'add2'])
Use data from the context
Also for accessing data from the context, there are different approaches:
One can use elements from the
wg.ctx.x
, directly, e.g., as inputs for other tasks
add2 = wg1.add_task(add, "add2", x=wg1.ctx.x, y=3)
# 2. Similarly, nested context keys can be accessed, such as ``wg2.ctx.sum.add1``
print(wg.ctx.sum.add1)
# Gives: SocketAny(name='add1', value=None)
# 3. One can use the `get_context` task to get the data from ctx. **This task will be shown in the GUI**
#
wg3.add_task("workgraph.get_context", name="get_ctx1", key="sum.add1")
wg.show()
wg.to_html()
#
# 2. One can export the data from context to the graph builder outputs.
#
@task.graph()
def internal_add(x, y):
outputs1 = add(x=x, y=y)
outputs2 = add(x=outputs1.sum, y=5)
return outputs2.sum
# Usage in a main WorkGraph
wg_main = WorkGraph("main")
builder_task = wg_main.add_task(internal_add, x=10, y=20)
final_task = wg_main.add_task(add, x=builder_task.outputs.result, y=100)
# In this example, the context can serve as a bridge between the internal workings of a subgraph and its external
# interface. This allows selectively exposing internal results as clean, named outputs.
#
SocketAny(name='add1', value=None)
--------------------------------------------------------------------------------
WorkGraph: ctx_nested, PK: None, State: CREATED
--------------------------------------------------------------------------------
Tasks:
Name PK State
--------- ---- -------
add1 PLANNED
add2 PLANNED
graph_ctx PLANNED
--------------------------------------------------------------------------------
Context Tasks in the GUI
This example shows how context operations appear in the workflow visualization when using explicit context tasks instead of direct context access.
Note
Context tasks make data flow visible in the GUI, which is helpful for debugging and understanding workflow execution. Use them when you want explicit visibility of context operations.
wg = WorkGraph(name="context_gui_demo")
# Set initial context values
wg.ctx = {"x": 2, "multiplier": 10}
# Use context tasks - these appear as nodes in the GUI
get_x = wg.add_task("workgraph.get_context", name="get_x", key="x")
get_multiplier = wg.add_task(
"workgraph.get_context", name="get_multiplier", key="multiplier"
)
# Perform calculation using context values
add1 = wg.add_task(add, "add1", x=get_x.outputs.result, y=get_multiplier.outputs.result)
# Store result back to context - also appears in GUI
wg.add_task(
"workgraph.set_context",
name="store_result",
key="final_result",
value=add1.outputs.result,
)
wg.to_html()
wg.show()
--------------------------------------------------------------------------------
WorkGraph: context_gui_demo, PK: None, State: CREATED
--------------------------------------------------------------------------------
Tasks:
Name PK State
-------------- ---- -------
graph_ctx PLANNED
get_x PLANNED
get_multiplier PLANNED
add1 PLANNED
store_result PLANNED
--------------------------------------------------------------------------------
As shown in the GUI, the get_context
and set_context
tasks appear as
visible nodes in the workflow graph. This makes the data flow through context
explicit and traceable, unlike direct context access via wg.ctx.x
which
is invisible in the visualization.
wg.run()
print("State of WorkGraph : {}".format(wg.state))
print("Result of add1 : {}".format(wg.tasks.add1.outputs.result.value))
State of WorkGraph : FINISHED
Result of add1 : uuid: 590bde7f-bb27-4189-97db-0f30d631edca (pk: 985) value: 12
# Generate node graph from the AiiDA process
from aiida_workgraph.utils import generate_node_graph
generate_node_graph(wg.pk)
Note
If you pass data from one task to another task through the context, you may need to use wait
to wait for
the data to be ready. See waiting_on.
Total running time of the script: (0 minutes 1.100 seconds)