Common schemas for workflows #21
Replies: 11 comments 11 replies
SummaryWe created a repository for the results of our working group: The initial paper for the Python Workflow Definition is finally available on Digital Discovery: |
Current ToDos
|
Plan for extending the PWD for dynamic workflows:
|
Draft of the JSON formatInspiration from https://github.com/jan-janssen/while-loop-flowrep/blob/main/while.ipynb Suggestion - for node four the output has to be {
"version": "0.1.1",
"nodes": [
{
"id": 0,
"type": "while",
"value": {
"nodes": [
{"id": 0, "type": "function", "value": "workflow.function_body"},
{"id": 1, "type": "test", "value": "workflow.condition"},
{"id": 2, "type": "input", "name": "x"},
{"id": 3, "type": "input", "name": "limit"},
{"id": 4, "type": "output", "name": "x"},
],
"edges": [
{"target": 4, "targetPort": null, "source": 0, "sourcePort": null},
{"target": 0, "targetPort": "x", "source": 2, "sourcePort": null},
{"target": 1, "targetPort": "x", "source": 2, "sourcePort": null},
{"target": 1, "targetPort": "limit", "source": 3, "sourcePort": null},
]
}
},
{"id": 1, "type": "input", "value": 0, "name": "x"},
{"id": 2, "type": "input", "value": 5, "name": "limit"},
{"id": 3, "type": "output", "name": "result"}
],
"edges": [
{"target": 0, "targetPort": "x", "source": 1, "sourcePort": null},
{"target": 0, "targetPort": "limit", "source": 2, "sourcePort": null},
{"target": 3, "targetPort": null, "source": 0, "sourcePort": "result"},
]
} |
|
Before going into the discussion of the while loop, we discussed that we should support nesting of workflows written in JSON. Here is a suggested format: {
"version": "0.2.0",
"workflows": {
"prod_div": {
"nodes": [
{"id": 0, "type": "function", "value": "workflow.get_prod_and_div"},
{"id": 1, "type": "function", "value": "workflow.get_sum"},
{"id": 2, "type": "function", "value": "workflow.get_square"},
{"id": 3, "type": "input", "value": 1, "name": "x"},
{"id": 4, "type": "input", "value": 2, "name": "y"},
{"id": 5, "type": "output", "name": "result"}
],
"edges": [
{"target": 0, "targetPort": "x", "source": 3, "sourcePort": null},
{"target": 0, "targetPort": "y", "source": 4, "sourcePort": null},
{"target": 1, "targetPort": "x", "source": 0, "sourcePort": "prod"},
{"target": 1, "targetPort": "y", "source": 0, "sourcePort": "div"},
{"target": 2, "targetPort": "x", "source": 1, "sourcePort": null},
{"target": 5, "targetPort": null, "source": 2, "sourcePort": null}
]
},
"main": {
"nodes": [
{"id": 0, "type": "workflow", "value": "prod_div"},
{"id": 1, "value": 1, "type": "input", "name": "a"},
{"id": 2, "value": 2, "type": "input", "name": "b"},
{"id": 3, "type": "workflow", "value": "example.json:main"},
{"id": 4, "type": "output", "name": "final_result"}
],
"edges": [
{"target": 0, "targetPort": "x", "source": 1, "sourcePort": null},
{"target": 0, "targetPort": "y", "source": 2, "sourcePort": null},
{"target": 3, "targetPort": "inp", "source": 0, "sourcePort": "result"},
{"target": 4, "targetPort": null, "source": 3, "sourcePort": "out"}
]
}
}
}Discussion:
This essentially allows for grouping of nodes into logical workflows, allows for arbitrary nesting levels for workflow calling, and to define libraries of common workflows. |
Example to generate nested flowsfrom jobflow import job, Flow, run_locally
@job
def add(x, y):
return x + y
def get_flow(x,y):
job1=add(x,y)
job2=add(job1.output,y)
return Flow([job1,job2],output=job2.output)
flow1=get_flow(1,2)
flow2=get_flow(flow1.output,4)
final_flow=Flow([flow1,flow2],output=flow2.output)
print(final_flow.as_dict())
run_locally(final_flow) |
|
in order to make the PWD files easily recognisable, we suggested to have a conventional name such as |
|
On a different topic, the while loop. Here is a possible implementation. However, the issue is that this might imply that if the body of the while loop is itself a workflow (written as a JSON file), to write it we write JSON (top level) calling python, calling JSON, etc etc. Here is anyways the "pseudo" code, to stimulate discussions. {
"version": "0.2.0",
"workflows": {
"main": {
"nodes": [
{"id": 0, "value": "filename.DOS", "type": "input:from_file", "name": "DOS"},
{"id": 1, "value": 0.0, "type": "input", "name": "EF_left"},
{"id": 2, "value": 10.0, "type": "input", "name": "EF_right"},
{"id": 3, "value": 0.001, "type": "input", "name": "convergence_threshold"},
{"id": 4, "value": 1.0, "type": "input", "name": "target_num_electrons"},
{"id": 5, "type": "while", "value": "function:body_f", "name": "while_loop", "condition_f": "condition_f", "initializer": "initializer_f", "finalizer": "finalizer_f"},
{"id": 6, "type": "output", "name": "EF"},
{"id": 7, "type": "output", "name": "num_electrons"}
],
"edges": [
{"source": 0, "sourcePort": null, "target": 5, "targetPort": "dos"},
{"source": 1, "sourcePort": null, "target": 5, "targetPort": "EF_left"},
{"source": 2, "sourcePort": null, "target": 5, "targetPort": "EF_right"},
{"source": 3, "sourcePort": null, "target": 5, "targetPort": "target_num_electrons"},
{"source": 4, "sourcePort": null, "target": 5, "targetPort": "convergence_threshold"},
{"source": 5, "sourcePort": "EF", "target": 6, "targetPort": null},
{"source": 5, "sourcePort": "num_electrons", "target": 7, "targetPort": null}
]
}
}
}Python functions: def initializer_f(input_ports, ctx):
# Here you can do checks, or initialize other ctx variables.
ctx['EF_left'] = input_ports['EF_left']
ctx['EF_right'] = input_ports['EF_right']
ctx['EF'] = (ctx['EF_left'] + ctx['EF_right']) / 2
return ctx
def condition_f(input_ports, ctx):
return (ctx['EF_right'] - ctx['EF_left']) > input_ports['convergence_threshold']
def body_f(input_ports, ctx):
ctx['num_electrons'] = compute_num_electrons(input_ports['dos'], ctx['EF']) ### ISSUE: this should be another workflow! Could be a JSON, not a python function...
if ctx['num_electrons'] < input_ports['target_num_electrons']:
ctx['EF_left'] = ctx['EF']
else:
ctx['EF_right'] = ctx['EF']
ctx['EF'] = (ctx['EF_left'] + ctx['EF_right']) / 2
return ctx
def finalizer_f(input_ports, ctx):
return {"EF": ctx['EF'], "num_electrons": ctx['num_electrons']} |
|
Tomorrow I would request to briefly discuss my post in #29 whenever is convenient |
|
Long overdue update on the PerQueue implementation of PWD A PWD reader and writer has been developed in PerQueue, which can generate a functioning workflow within PerQueue. This also inspired explicit use of data ports for advanced data control, which currently works for most cases. It can read in the quantum espresso example from the python workflow definition and write this workflow again from the workflow generated from the read-in, and run a simplified version of this without explicit data porting. It can also submit this workflow with the option of generating temporary required files with submitted functions, if these do not exist already for easy workflow setup (note exception in point 2)*. For now, this submission will assume a local job submission with 5 minutes time limit, but this can be changed easily by the user.
On a side note, PerQueue now supports any function name, and will import it from a specified script, where it prior to this week would only support functions named "main" (which it will default to, if function is not specified for backwards compatibility). Examples of these implementations have been placed in the perqueue/examples folder, and all of this can be found on the PWD_abstraction branch on the gitlab repository, https://gitlab.com/asm-dtu/perqueue.git |
|
See #33 for an ongoing discussion on supporting PWD-driven workflows in FINALES. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
This thread can be used to record notes from general discussions in the working group on common workflow schemas.
Pre-workshop discussion: #16
All reactions