Skip to content

Commit 6bf5c50

Browse files
committed
Namespace socket and tab completion (#388)
AiiDA has a namespace port and supports nested ports, e.g., the "base.pw.metadata" in the `PwRelaxWorkChain.` Previously, WorkGraph flat the nested port, and make every port on the top-level. There are many disadvantages: - one needs to use the dict-style code to access the socket, e.g., `outputs["relax.output_structure"]`. - tab-completion is not possible, because "." is in the key. This PR uses the `NodeSocketNamespace` from the latest `node-graph` to support the nested sockets, i.e., the namespace in AiiDA. The top-level `inputs` and `outputs` are also namespace sockets now. The auto-completion is also supported.
1 parent df7374b commit 6bf5c50

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+767
-1031
lines changed

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,19 @@ def multiply(x, y):
4949
wg = WorkGraph("test_add_multiply")
5050
wg.add_task(add, name="add1")
5151
wg.add_task(multiply, name="multiply1")
52-
wg.add_link(wg.tasks["add1"].outputs["result"], wg.tasks["multiply1"].inputs["x"])
52+
wg.add_link(wg.tasks.add1.outputs.result, wg.tasks.multiply1.inputs.x)
5353

5454
```
5555

56-
Prepare inputs and submit the workflow:
56+
Prepare inputs and run the workflow:
5757

5858
```python
5959
from aiida import load_profile
6060

6161
load_profile()
6262

63-
wg.submit(inputs = {"add1": {"x": 2, "y": 3}, "multiply1": {"y": 4}}, wait=True)
64-
print("Result of multiply1 is", wg.tasks["multiply1"].outputs[0].value)
63+
wg.run(inputs = {"add1": {"x": 2, "y": 3}, "multiply1": {"y": 4}})
64+
print("Result of multiply1 is", wg.tasks.multiply1.outputs.result.value)
6565
```
6666
## Web ui
6767
To use the web ui, first install the web ui package:

docs/gallery/autogen/quick_start.py

+8-176
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def multiply(x, y):
107107
wg = WorkGraph("add_multiply_workflow")
108108
add_task = wg.add_task(add, name="add1")
109109
# link the output of the `add` task to one of the `x` input of the `multiply` task.
110-
wg.add_task(multiply, name="multiply1", x=add_task.outputs["result"])
110+
wg.add_task(multiply, name="multiply1", x=add_task.outputs.result)
111111

112112
# export the workgraph to html file so that it can be visualized in a browser
113113
wg.to_html()
@@ -128,8 +128,8 @@ def multiply(x, y):
128128
)
129129

130130
print("State of WorkGraph: {}".format(wg.state))
131-
print("Result of add : {}".format(wg.tasks["add1"].outputs[0].value))
132-
print("Result of multiply : {}".format(wg.tasks["multiply1"].outputs[0].value))
131+
print("Result of add : {}".format(wg.tasks.add1.outputs.result.value))
132+
print("Result of multiply : {}".format(wg.tasks.multiply1.outputs.result.value))
133133

134134

135135
######################################################################
@@ -141,172 +141,6 @@ def multiply(x, y):
141141
generate_node_graph(wg.pk)
142142

143143

144-
######################################################################
145-
# Remote job
146-
# ----------
147-
#
148-
# The ``PythonJob`` is a built-in task that allows users to run Python
149-
# functions on a remote computer.
150-
#
151-
# In this case, we use define the task using normal function instead of
152-
# ``calcfunction``. Thus, user does not need to install AiiDA on the
153-
# remote computer.
154-
#
155-
156-
from aiida_workgraph import WorkGraph, task
157-
158-
# define add task
159-
@task()
160-
def add(x, y):
161-
return x + y
162-
163-
164-
# define multiply task
165-
@task()
166-
def multiply(x, y):
167-
return x * y
168-
169-
170-
wg = WorkGraph("second_workflow")
171-
# You might need to adapt the label to python3 if you use this as your default python
172-
wg.add_task("PythonJob", function=add, name="add", command_info={"label": "python"})
173-
wg.add_task(
174-
"PythonJob",
175-
function=multiply,
176-
name="multiply",
177-
x=wg.tasks["add"].outputs[0],
178-
command_info={"label": "python"},
179-
)
180-
181-
# export the workgraph to html file so that it can be visualized in a browser
182-
wg.to_html()
183-
# visualize the workgraph in jupyter-notebook
184-
# wg
185-
186-
187-
######################################################################
188-
# Submit the workgraph
189-
# ~~~~~~~~~~~~~~~~~~~~
190-
#
191-
# **Code**: We can set the ``computer`` to the remote computer where we
192-
# want to run the job. This will create a code ``python@computer`` if not
193-
# exists. Of course, you can also set the ``code`` directly if you have
194-
# already created the code.
195-
#
196-
# **Data**: Users can (and is recoomaneded) use normal Python data as
197-
# input. The workgraph will transfer the data to AiiDA data
198-
# (``PickledData``) using pickle.
199-
#
200-
# **Python Version**: since pickle is used to store and load data, the
201-
# Python version on the remote computer should match the one used in the
202-
# localhost. One can use conda to create a virtual environment with the
203-
# same Python version. Then activate the environment before running the
204-
# script.
205-
#
206-
# .. code:: python
207-
#
208-
# # For real applications, one can pass metadata to the scheduler to activate the conda environment
209-
# metadata = {
210-
# "options": {
211-
# 'custom_scheduler_commands' : 'module load anaconda\nconda activate py3.11\n',
212-
# }
213-
# }
214-
#
215-
216-
from aiida_workgraph.utils import generate_node_graph
217-
218-
# ------------------------- Submit the calculation -------------------
219-
# For real applications, one can pass metadata to the scheduler to activate the conda environment
220-
metadata = {
221-
"options": {
222-
# 'custom_scheduler_commands' : 'module load anaconda\nconda activate py3.11\n',
223-
"custom_scheduler_commands": "",
224-
}
225-
}
226-
227-
wg.submit(
228-
inputs={
229-
"add": {"x": 2, "y": 3, "computer": "localhost", "metadata": metadata},
230-
"multiply": {"y": 4, "computer": "localhost", "metadata": metadata},
231-
},
232-
wait=True,
233-
)
234-
# ------------------------- Print the output -------------------------
235-
print(
236-
"\nResult of multiply is {} \n\n".format(
237-
wg.tasks["multiply"].outputs["result"].value
238-
)
239-
)
240-
# ------------------------- Generate node graph -------------------
241-
generate_node_graph(wg.pk)
242-
243-
244-
######################################################################
245-
# Use parent folder
246-
# ~~~~~~~~~~~~~~~~~
247-
#
248-
# The parent_folder parameter allows a task to access the output files of
249-
# a parent task. This feature is particularly useful when you want to
250-
# reuse data generated by a previous computation in subsequent
251-
# computations. In the following example, the multiply task uses the
252-
# ``result.txt`` file created by the add task.
253-
#
254-
# By default, the content of the parent folder is symlinked to the working
255-
# directory. In the function, you can access the parent folder using the
256-
# relative path. For example, ``./parent_folder/result.txt``.
257-
#
258-
259-
from aiida_workgraph import WorkGraph, task
260-
261-
# define add task
262-
@task()
263-
def add(x, y):
264-
z = x + y
265-
with open("result.txt", "w") as f:
266-
f.write(str(z))
267-
268-
269-
# define multiply task
270-
@task()
271-
def multiply(x, y):
272-
with open("parent_folder/result.txt", "r") as f:
273-
z = int(f.read())
274-
return x * y + z
275-
276-
277-
wg = WorkGraph("third_workflow")
278-
# You might need to adapt the label to python3 if you use this as your default python
279-
wg.add_task("PythonJob", function=add, name="add", command_info={"label": "python"})
280-
wg.add_task(
281-
"PythonJob",
282-
function=multiply,
283-
name="multiply",
284-
parent_folder=wg.tasks["add"].outputs["remote_folder"],
285-
command_info={"label": "python"},
286-
)
287-
288-
wg.to_html()
289-
290-
291-
######################################################################
292-
# Submit the calculation
293-
#
294-
295-
# ------------------------- Submit the calculation -------------------
296-
wg.submit(
297-
inputs={
298-
"add": {"x": 2, "y": 3, "computer": "localhost"},
299-
"multiply": {"x": 3, "y": 4, "computer": "localhost"},
300-
},
301-
wait=True,
302-
)
303-
print(
304-
"\nResult of multiply is {} \n\n".format(
305-
wg.tasks["multiply"].outputs["result"].value
306-
)
307-
)
308-
309-
310144
######################################################################
311145
# CalcJob and WorkChain
312146
# ---------------------
@@ -341,7 +175,7 @@ def multiply(x, y):
341175
wg = WorkGraph("test_add_multiply")
342176
add1 = wg.add_task(ArithmeticAddCalculation, name="add1", x=Int(2), y=Int(3), code=code)
343177
add2 = wg.add_task(ArithmeticAddCalculation, name="add2", y=Int(3), code=code)
344-
wg.add_link(wg.tasks["add1"].outputs["sum"], wg.tasks["add2"].inputs["x"])
178+
wg.add_link(wg.tasks.add1.outputs.sum, wg.tasks.add2.inputs.x)
345179
wg.to_html()
346180

347181

@@ -350,7 +184,7 @@ def multiply(x, y):
350184
#
351185

352186
wg.submit(wait=True)
353-
print("Result of task add1: {}".format(wg.tasks["add2"].outputs["sum"].value))
187+
print("Result of task add1: {}".format(wg.tasks.add2.outputs.sum.value))
354188

355189
from aiida_workgraph.utils import generate_node_graph
356190

@@ -413,7 +247,7 @@ def add_multiply(x, y, z):
413247
wg = WorkGraph()
414248
wg.add_task(add, name="add", x=x, y=y)
415249
wg.add_task(multiply, name="multiply", x=z)
416-
wg.add_link(wg.tasks["add"].outputs["result"], wg.tasks["multiply"].inputs["y"])
250+
wg.add_link(wg.tasks.add.outputs.result, wg.tasks.multiply.inputs.y)
417251
# don't forget to return the `wg`
418252
return wg
419253

@@ -431,7 +265,7 @@ def add_multiply(x, y, z):
431265
add_multiply1 = wg.add_task(add_multiply, x=Int(2), y=Int(3), z=Int(4))
432266
add_multiply2 = wg.add_task(add_multiply, x=Int(2), y=Int(3))
433267
# link the output of a task to the input of another task
434-
wg.add_link(add_multiply1.outputs["multiply"], add_multiply2.inputs["z"])
268+
wg.add_link(add_multiply1.outputs.multiply, add_multiply2.inputs.z)
435269
wg.submit(wait=True)
436270
print("WorkGraph state: ", wg.state)
437271

@@ -440,9 +274,7 @@ def add_multiply(x, y, z):
440274
# Get the result of the tasks:
441275
#
442276

443-
print(
444-
"Result of task add_multiply1: {}".format(add_multiply1.outputs["multiply"].value)
445-
)
277+
print("Result of task add_multiply1: {}".format(add_multiply1.outputs.multiply.value))
446278

447279
generate_node_graph(wg.pk)
448280

docs/gallery/built-in/autogen/shelljob.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
wg.submit(wait=True)
3232

3333
# Print out the result:
34-
print("\nResult: ", date_task.outputs["stdout"].value.get_content())
34+
print("\nResult: ", date_task.outputs.stdout.value.get_content())
3535

3636
# %%
3737
# Under the hood, an AiiDA ``Code`` instance ``date`` will be created on the ``localhost`` computer. In addition, it is also
@@ -93,7 +93,7 @@
9393
wg.submit(wait=True)
9494

9595
# Print out the result:
96-
print("\nResult: ", date_task.outputs["stdout"].value.get_content())
96+
print("\nResult: ", date_task.outputs.stdout.value.get_content())
9797

9898
# %%
9999
# Running a shell command with files as arguments
@@ -117,7 +117,7 @@
117117
wg.submit(wait=True)
118118

119119
# Print out the result:
120-
print("\nResult: ", cat_task.outputs["stdout"].value.get_content())
120+
print("\nResult: ", cat_task.outputs.stdout.value.get_content())
121121

122122
# %%
123123
# Create a workflow
@@ -157,7 +157,7 @@ def parser(dirpath):
157157
name="expr_2",
158158
command="expr",
159159
arguments=["{result}", "*", "{z}"],
160-
nodes={"z": Int(4), "result": expr_1.outputs["result"]},
160+
nodes={"z": Int(4), "result": expr_1.outputs.result},
161161
parser=PickledData(parser),
162162
parser_outputs=[{"name": "result"}],
163163
)

docs/gallery/concept/autogen/socket.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ def multiply(x, y):
2525
return x * y
2626

2727

28-
print("Input ports: ", multiply.task().inputs.keys())
29-
print("Output ports: ", multiply.task().outputs.keys())
28+
print("Input ports: ", multiply.task().get_input_names())
29+
print("Output ports: ", multiply.task().get_output_names())
3030

3131
multiply.task().to_html()
3232

@@ -49,8 +49,8 @@ def add_minus(x, y):
4949
return {"sum": x + y, "difference": x - y}
5050

5151

52-
print("Input ports: ", add_minus.task().inputs.keys())
53-
print("Ouput ports: ", add_minus.task().outputs.keys())
52+
print("Input ports: ", add_minus.task().get_input_names())
53+
print("Ouput ports: ", add_minus.task().get_output_names())
5454
add_minus.task().to_html()
5555

5656

@@ -85,8 +85,7 @@ def add(x: int, y: float) -> float:
8585
return x + y
8686

8787

88-
for input in add.task().inputs:
89-
print("{:30s}: {:20s}".format(input.name, input.identifier))
88+
print("inputs: ", add.task().inputs)
9089

9190

9291
######################################################################
@@ -140,7 +139,7 @@ def add(x, y):
140139

141140
def create_sockets(self):
142141
# create a General port.
143-
inp = self.inputs.new("workgraph.Any", "symbols")
142+
inp = self.add_input("workgraph.Any", "symbols")
144143
# add a string property to the port with default value "H".
145144
inp.add_property("String", "default", default="H")
146145

0 commit comments

Comments
 (0)