If, while and branching
Here we will introduce how if statements and while loops are represented in TaskBlaster by making use of branching. Since this is an advanced topic it is suggested that you first read about the conceptual difference between static and dynamical workflows in Dynamical workflows.
A single if-statement
Consider following piece of pseudo code:
a = A()
if B(a):
c = C(a)
else:
d = D(a)
If A, B, C, and D are tasks, the outcome of B determines whether C or D will be executed. C and D will never both run, and you can’t know which one will run until A and B are completed. So, this code needs to be part of a dynamic workflow.
Let us now try to create the workflow above in TaskBlaster step by step. First initialize a repository, and write dummy tasks to tasks.py to represent tasks A, B, C, and D. For convenience, we also include task E for further tutorial in this page.
$ tb init
Created repository using module "taskblaster.repository" in "/home/myuser/tmprepo".
def A():
return 1
def B(a):
return a > 1
def C(a):
return a + 2
def D(a):
return a + 3
def E(a):
return a + 4
And write the workflow corresponding to the pseudocode above. Note that at this stage of the tutorial, the exact Taskblaster syntax is irrelevant (it will be revisited below in more detail). Crucial thing here is that this is the exact Taskblaster representation of the pseudo code (for a graphical representation see Dynamical workflows).
import taskblaster as tb
@tb.workflow
class DynamicWorkflow:
@tb.task
def A(self):
return tb.node('A')
@tb._if(true='Cbranch', false='Dbranch')
@tb.task
def B(self):
return tb.node('B', a=self.A)
@tb.branch('Cbranch')
@tb.task
def C(self):
return tb.node('C', a=self.A)
@tb.branch('Dbranch')
@tb.task
def D(self):
return tb.node('D', a=self.A)
def workflow(runner):
runner.run_workflow(DynamicWorkflow())
We have introduced two new decorators to tasks. Firstly, the @tb.branch(<branch name>)
decorator, which will assign a task
to a particular branch. If the decorator is omitted, the default branch will be entry
.
Secondly, we utilized the @tb._if(true=<if branch>, false=<else branch>)
decorator. The task with this decorator
will now return a result, and whether that evaluates to True or False, a particular branch is chosen. Note the underscore
in _if
because if
cannot be used since it is a reserved word. Same applies to lower case true
and false
parameters.
We can now execute the workflow:
$ tb workflow workflow.py entry: add new 0/0 tree/A if: add new 0/1 tree/B T=Cbranch F=Dbranch
We see that only A
and B
tasks were created. We see this entry:
text, which
is the default branch for each task, if they are not given any other branch name
by the @tb.branch
decorator.
We also see that B has some extra markings
on it, if
on the left hand side, and information about the control flow on
the right hand side. The blue color of the True and False results
indicate that this branch has not been decided yet (because the B
“if-task”
is not yet done).
We can observe the current status of the project:
$ tb ls state info tags worker time folder ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── new 0/0 tree/A new 0/1 tree/B
The above relates us back to the initial paragraph of the static workflow. The task dependency graph has now been generated as far as it can, before it encountered a branching statement. This is as far as static workflows can get us, thus to execute one branch, is to execute a static workflow.
We can now run and observe the workflow:
$ tb run .
Starting worker rank=000 size=001
[rank=000 2025-03-17 10:08:30 N/A-0/1] Worker class: —
[rank=000 2025-03-17 10:08:30 N/A-0/1] Required tags: —
[rank=000 2025-03-17 10:08:30 N/A-0/1] Supported tags: —
[rank=000 2025-03-17 10:08:30 N/A-0/1] name: None
tags: —
required_tags: —
resources: None
max_tasks: None
subworker_size: None
subworker_count: None
wall_time: None
[rank=000 2025-03-17 10:08:30 N/A-0/1] Main loop
[rank=000 2025-03-17 10:08:30 N/A-0/1] Running A ...
[rank=000 2025-03-17 10:08:30 N/A-0/1] Task A finished in 0:00:00.001097
[rank=000 2025-03-17 10:08:30 N/A-0/1] Running B ...
[rank=000 2025-03-17 10:08:30 N/A-0/1] Task B finished in 0:00:00.000499
[rank=000 2025-03-17 10:08:30 N/A-0/1] No available tasks, end worker main loop
$ tb ls state info tags worker time folder ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── done 0/0 N/A-0/1 00:00:00 tree/A done 1/1 N/A-0/1 00:00:00 tree/B
We note that the task to determine the branching is now ready, so we may continue with the workflow execution.
$ tb workflow workflow.py entry: have done 0/0 tree/A if: have done 1/1 tree/B T=Cbranch F=Dbranch jump: Dbranch Dbranch: add new 2/2 tree/D
The workflow command will discover that the task associated with the if statement
has now been evaluated, and it will make a jump. Therefore, it will execute
the new branch called Dbranch, which is highlighted by making it green,
and the rejected branch was made red. We also see, that due to creation of the
Dbranch (in similar static sense as the entry-branch above) we obtain one
new task, namely D
.
We can now run this D
task, to complete the workflow.
$ tb run .
Starting worker rank=000 size=001
[rank=000 2025-03-17 10:08:31 N/A-0/1] Worker class: —
[rank=000 2025-03-17 10:08:31 N/A-0/1] Required tags: —
[rank=000 2025-03-17 10:08:31 N/A-0/1] Supported tags: —
[rank=000 2025-03-17 10:08:31 N/A-0/1] name: None
tags: —
required_tags: —
resources: None
max_tasks: None
subworker_size: None
subworker_count: None
wall_time: None
[rank=000 2025-03-17 10:08:31 N/A-0/1] Main loop
[rank=000 2025-03-17 10:08:31 N/A-0/1] Running D ...
[rank=000 2025-03-17 10:08:31 N/A-0/1] Task D finished in 0:00:00.001084
[rank=000 2025-03-17 10:08:31 N/A-0/1] No available tasks, end worker main loop
Running the workflow command has no further effect.
$ tb workflow workflow.py entry: have done 0/0 tree/A if: have done 1/1 tree/B T=Cbranch F=Dbranch jump: Dbranch Dbranch: have done 2/2 tree/D
as all of the tasks are done.
$ tb ls state info tags worker time folder ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── done 0/0 N/A-0/1 00:00:00 tree/A done 1/1 N/A-0/1 00:00:00 tree/B done 2/2 N/A-0/1 00:00:00 tree/D
Converging from multiple branches
Very often, we would like to take a detour with branching, and then return to the master control flow. In the following we will use the Collatz sequence as an example. The Collatz sequence is generated, starting from any positive number and following two simple rules:
If the number is even, divide by two
If the number is odd, multiply by three and add 1.
The Collatz conjecture states that the sequence will always lead to the number 1.
We will now recreate one iteration of the Collatz sequence (in the next section, we will iterate this further).
Let’s define very simple tasks.
def is_even(number):
return number % 2 == 0
def divide_by_two(number):
return number // 2
def three_n_plus_one(number):
return 3 * number + 1
def results(number, sequence):
return number, sequence + [number]
def sequence(sequence, number):
return [*sequence, number]
def stop_iteration(number):
return number == 1
The pseudo code to do one iteration can be written as:
def CollatzIteration(number, sequence):
if is_even(number):
new_number = divide_by_two(number)
else:
new_number = three_n_plus_one(number)
return results(new_number, sequence)
Let us write this workflow with Taskblaster. Following the same basic steps, we initialize the repository.
$ tb init
Created repository using module "taskblaster.repository" in "/home/myuser/tmprepo".
Write the workflow for one iteration of the Collatz to the file
converging_workflow.py
:
import taskblaster as tb
@tb.workflow
class CollatzIteration:
number = tb.var()
sequence = tb.var()
@tb._if(true='even_branch', false='odd_branch')
@tb.task
def IsEven(self):
return tb.node('is_even', number=self.number)
@tb.branch('even_branch')
@tb.jump('results')
@tb.task
def even_task(self):
return tb.node('divide_by_two', number=self.number)
@tb.branch('odd_branch')
@tb.jump('results')
@tb.task
def odd_task(self):
return tb.node('three_n_plus_one', number=self.number)
@tb.branch('results')
@tb.task
def gather_result(self):
return tb.node(
'results',
number=self.Phi(
even_branch=self.even_task, odd_branch=self.odd_task
),
sequence=self.sequence,
)
def workflow(runner):
runner.run_workflow(CollatzIteration(number=5, sequence=[]))
We have introduced a new @tb.jump(<branch>)
keyword, which causes control flow to
jump immediately from current branch to another.
The usecases of jump include returning to main control flow after
diverging into multiple branches.
Note
One is allowed to have exactly one branching statement inside a branch, that is only
one if or jump is allowed per branch. Below, is an illustration of valid
branch
and an invalid
branch with two branching statement.
@tb.branch('valid')
@tb.jump('target')
def taskA(self):
...
@tb.branch('valid')
@tb.jump('target')
def taskB(self):
...
@tb.branch('invalid')
@tb.jump('target')
def taskC(self):
...
@tb.branch('invalid')
@tb.if(true='target', false='otherbranch')
def taskD(self):
...
We have also introduced a new keyword, self.Phi(<branchname>=<self.task_name>, ...)
,
which is used to select the correct branch for children Tasks with
branching parent Tasks. Phi
provides a mechanism to reference data
before it exists, and resolve these inputs once the task has been completed,
passing the correct branch to the children tasks.
That is, when entering a branch with a task with a Phi-operator, the rule is that
it will select the input routing based on which branch it came from. In this case,
that is triggered by the jump statements. For the explicit example above the gather_result
task will thus
execute the function results
with the input argument number given by the output from the èven_task
if the even_branch was executed or by the output from the odd_task
if the odd_branch was executed.
Now run the workflow
$ tb workflow converging_workflow.py entry:if: add new 0/0 tree/IsEven T=even_branch F=odd_branch
We can now run this task, and again, rerun the workflow to make a new iteration. We repeat these steps, until everything is done.
$ tb run .
Starting worker rank=000 size=001
[rank=000 2025-03-17 10:08:31 N/A-0/1] Worker class: —
[rank=000 2025-03-17 10:08:31 N/A-0/1] Required tags: —
[rank=000 2025-03-17 10:08:31 N/A-0/1] Supported tags: —
[rank=000 2025-03-17 10:08:31 N/A-0/1] name: None
tags: —
required_tags: —
resources: None
max_tasks: None
subworker_size: None
subworker_count: None
wall_time: None
[rank=000 2025-03-17 10:08:31 N/A-0/1] Main loop
[rank=000 2025-03-17 10:08:31 N/A-0/1] Running IsEven ...
[rank=000 2025-03-17 10:08:31 N/A-0/1] Task IsEven finished in 0:00:00.001231
[rank=000 2025-03-17 10:08:31 N/A-0/1] No available tasks, end worker main loop
$ tb workflow converging_workflow.py entry:if: have done 0/0 tree/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: add new 1/1 tree/odd_task jump: results results: add new 1/2 tree/gather_result
$ tb run .
Starting worker rank=000 size=001
[rank=000 2025-03-17 10:08:31 N/A-0/1] Worker class: —
[rank=000 2025-03-17 10:08:31 N/A-0/1] Required tags: —
[rank=000 2025-03-17 10:08:31 N/A-0/1] Supported tags: —
[rank=000 2025-03-17 10:08:31 N/A-0/1] name: None
tags: —
required_tags: —
resources: None
max_tasks: None
subworker_size: None
subworker_count: None
wall_time: None
[rank=000 2025-03-17 10:08:31 N/A-0/1] Main loop
[rank=000 2025-03-17 10:08:31 N/A-0/1] Running odd_task ...
[rank=000 2025-03-17 10:08:31 N/A-0/1] Task odd_task finished in 0:00:00.001139
[rank=000 2025-03-17 10:08:31 N/A-0/1] Running gather_result ...
[rank=000 2025-03-17 10:08:31 N/A-0/1] Task gather_result finished in 0:00:00.000581
[rank=000 2025-03-17 10:08:31 N/A-0/1] No available tasks, end worker main loop
$ tb ls --sort=topo state info tags worker time folder ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── done 0/0 N/A-0/1 00:00:00 tree/IsEven done 1/1 N/A-0/1 00:00:00 tree/odd_task done 2/2 N/A-0/1 00:00:00 tree/gather_result
Let’s see the output, which is 16 as expected, because input 5 is odd, we calculate 5*3+1=16.
$ tb view tree/gather_result
name: gather_result
location: /home/myuser/tmprepo/tree/gather_result
state: done
target: results(…)
wait for: 0 dependencies
depth: 2
source workflow: <root workflow>
frozen by: (not frozen)
latest handled inputs:
None
handlers:
[]
handler data:
<None>
parents:
IsEven
odd_task
input:
["results", {"__tb_implicit_remove__": [["IsEven", {"__tb_type__": "ref", "index": [], "name": "IsEven"}]], "number": {"__tb_type__": "ref", "index": [], "name": "odd_task"}, "sequence": []}]
output:
[16, [16]]
Run information:
Worker name: N/A-0/1
Start time: 2025-03-17 10:08:31
End time: 2025-03-17 10:08:31
Duration: 0:00:00
Error: None
No custom actions defined for this task.
We can illustrate the control flow and task parameter routing now with the following figure (Similar to Dynamical workflows the parameter routing is represented by the dashed green arrows and the control flow by the thick solid arrows).
Note how we in all cases jump to the last branch, which then collects the results. In the next section, we will go into even more complicated dynamical workflows, as we will finish the Collatz sequence. That is we will further use this dynamical workflow, as a dynamic subworkflow, on a dynamic workflow.
Dynamical subworkflows inside dynamical workflows
This will be the grande finale of the tutorial. We will utilize the workflow created above, the branching workflow, inside a workflow which demonstrates a while loop.
First initialize a new repository
Then write the following workflow to the file collatz2_workflow.py:
import taskblaster as tb
@tb.workflow
class CollatzIteration:
number = tb.var()
@tb._if(true='even_branch', false='odd_branch')
@tb.task
def IsEven(self):
return tb.node('is_even', number=self.number)
@tb.branch('even_branch')
@tb.jump('result')
@tb.task
def even_task(self):
return tb.node('divide_by_two', number=self.number)
@tb.branch('odd_branch')
@tb.jump('result')
@tb.task
def odd_task(self):
return tb.node('three_n_plus_one', number=self.number)
@tb.branch('result')
@tb.fixedpoint
@tb.task
def result(self):
return tb.node(
'define',
obj=self.Phi(even_branch=self.even_task, odd_branch=self.odd_task),
)
@tb.workflow
class CollatzSequence:
number = tb.var()
@tb.branch('entry', loop=True)
@tb.subworkflow
def iteration(self):
return CollatzIteration(
number=self.Phi(default=self.number, entry=self.iteration.result)
)
@tb.branch('entry', loop=True)
@tb.task
def sequence(self):
return tb.node(
'sequence',
sequence=self.Phi(default=[self.number], entry=self.sequence),
number=self.iteration.result,
)
@tb.branch('entry', loop=True)
@tb._if(true='finish', false='entry')
@tb.task
def stop_iteration(self):
return tb.node('stop_iteration', number=self.iteration.result)
@tb.branch('finish')
@tb.task
def result(self):
return tb.node('define', obj=self.sequence)
def workflow(runner):
runner.run_workflow(CollatzSequence(number=5))
As you can see the main workflow CollatzSequence
now includes the CollatzIteration
as a subworkflow.
Note the keyword loop
to the branch decorator in CollatzSequence
. This keyword signifies that the branch can be revisited multiple times
which is the way you make while loops in TaskBlaster. In the workflow CollatzSequence
above first a single CollatzIteration will be executed.
The task sequence
adds the number to the list sequence. The first time the branch is visited the initial list sequence will just be the
input argument, the next time the branch is visited number will be added to the previously computed sequence. This is controlled by
sequence=self.Phi(default=[self.number], entry=self.sequence)
. Finally the task stop_iteration
checks if number=1, if so we are finished and the
result (sequence) is collected by the task result
, otherwise the entry branch is revisited for a new iteration.
Let’s see what happens when we execute this workflow:
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry: add new 0/? tree/iteration-001/result if: add new 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch entry: add new 0/1 tree/sequence-001 if: add new 0/1 tree/stop_iteration-001 T=finish F=entry state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False new 0/? tree/iteration-001/result ø new 0/1 tree/sequence-001 ø new 0/1 tree/stop_iteration-001 ø
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry:if: have done 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: add new 1/1 tree/iteration-001/odd_task jump: result result: update new 1/2 tree/iteration-001/result entry: update new 0/1 tree/sequence-001 if: update new 0/1 tree/stop_iteration-001 T=finish F=entry state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False done 1/1 N/A-0/1 00:00:00 tree/iteration-001/odd_task 16 done 2/2 N/A-0/1 00:00:00 tree/iteration-001/result 16 done 1/1 N/A-0/1 00:00:00 tree/sequence-001 [5, 16] done 1/1 N/A-0/1 00:00:00 tree/stop_iteration-001 False
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry:if: have done 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: have done 1/1 tree/iteration-001/odd_task jump: result result: have done 2/2 tree/iteration-001/result entry: have done 1/1 tree/sequence-001 if: have done 1/1 tree/stop_iteration-001 T=finish F=entry jump: entry entry: add new 1/? tree/iteration-002/result if: add new 2/2 tree/iteration-002/IsEven T=even_branch F=odd_branch entry: add new 2/3 tree/sequence-002 if: add new 1/2 tree/stop_iteration-002 T=finish F=entry state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False done 1/1 N/A-0/1 00:00:00 tree/iteration-001/odd_task 16 done 2/2 N/A-0/1 00:00:00 tree/iteration-001/result 16 done 1/1 N/A-0/1 00:00:00 tree/sequence-001 [5, 16] done 1/1 N/A-0/1 00:00:00 tree/stop_iteration-001 False done 2/2 N/A-0/1 00:00:00 tree/iteration-002/IsEven True new 1/? tree/iteration-002/result ø new 2/3 tree/sequence-002 ø new 1/2 tree/stop_iteration-002 ø
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry:if: have done 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: have done 1/1 tree/iteration-001/odd_task jump: result result: have done 2/2 tree/iteration-001/result entry: have done 1/1 tree/sequence-001 if: have done 1/1 tree/stop_iteration-001 T=finish F=entry jump: entry entry:if: have done 2/2 tree/iteration-002/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: add new 3/3 tree/iteration-002/even_task jump: result result: update new 2/3 tree/iteration-002/result entry: update new 2/3 tree/sequence-002 if: update new 1/2 tree/stop_iteration-002 T=finish F=entry state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False done 1/1 N/A-0/1 00:00:00 tree/iteration-001/odd_task 16 done 2/2 N/A-0/1 00:00:00 tree/iteration-001/result 16 done 1/1 N/A-0/1 00:00:00 tree/sequence-001 [5, 16] done 1/1 N/A-0/1 00:00:00 tree/stop_iteration-001 False done 2/2 N/A-0/1 00:00:00 tree/iteration-002/IsEven True done 3/3 N/A-0/1 00:00:00 tree/iteration-002/even_task 8 done 3/3 N/A-0/1 00:00:00 tree/iteration-002/result 8 done 3/3 N/A-0/1 00:00:00 tree/sequence-002 [5, 16, 8] done 2/2 N/A-0/1 00:00:00 tree/stop_iteration-002 False
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry:if: have done 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: have done 1/1 tree/iteration-001/odd_task jump: result result: have done 2/2 tree/iteration-001/result entry: have done 1/1 tree/sequence-001 if: have done 1/1 tree/stop_iteration-001 T=finish F=entry jump: entry entry:if: have done 2/2 tree/iteration-002/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 3/3 tree/iteration-002/even_task jump: result result: have done 3/3 tree/iteration-002/result entry: have done 3/3 tree/sequence-002 if: have done 2/2 tree/stop_iteration-002 T=finish F=entry jump: entry entry: add new 2/? tree/iteration-003/result if: add new 3/3 tree/iteration-003/IsEven T=even_branch F=odd_branch entry: add new 3/4 tree/sequence-003 if: add new 2/3 tree/stop_iteration-003 T=finish F=entry state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False done 1/1 N/A-0/1 00:00:00 tree/iteration-001/odd_task 16 done 2/2 N/A-0/1 00:00:00 tree/iteration-001/result 16 done 1/1 N/A-0/1 00:00:00 tree/sequence-001 [5, 16] done 1/1 N/A-0/1 00:00:00 tree/stop_iteration-001 False done 2/2 N/A-0/1 00:00:00 tree/iteration-002/IsEven True done 3/3 N/A-0/1 00:00:00 tree/iteration-002/even_task 8 done 3/3 N/A-0/1 00:00:00 tree/iteration-002/result 8 done 3/3 N/A-0/1 00:00:00 tree/sequence-002 [5, 16, 8] done 2/2 N/A-0/1 00:00:00 tree/stop_iteration-002 False done 3/3 N/A-0/1 00:00:00 tree/iteration-003/IsEven True new 2/? tree/iteration-003/result ø new 3/4 tree/sequence-003 ø new 2/3 tree/stop_iteration-003 ø
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry:if: have done 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: have done 1/1 tree/iteration-001/odd_task jump: result result: have done 2/2 tree/iteration-001/result entry: have done 1/1 tree/sequence-001 if: have done 1/1 tree/stop_iteration-001 T=finish F=entry jump: entry entry:if: have done 2/2 tree/iteration-002/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 3/3 tree/iteration-002/even_task jump: result result: have done 3/3 tree/iteration-002/result entry: have done 3/3 tree/sequence-002 if: have done 2/2 tree/stop_iteration-002 T=finish F=entry jump: entry entry:if: have done 3/3 tree/iteration-003/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: add new 4/4 tree/iteration-003/even_task jump: result result: update new 3/4 tree/iteration-003/result entry: update new 3/4 tree/sequence-003 if: update new 2/3 tree/stop_iteration-003 T=finish F=entry state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False done 1/1 N/A-0/1 00:00:00 tree/iteration-001/odd_task 16 done 2/2 N/A-0/1 00:00:00 tree/iteration-001/result 16 done 1/1 N/A-0/1 00:00:00 tree/sequence-001 [5, 16] done 1/1 N/A-0/1 00:00:00 tree/stop_iteration-001 False done 2/2 N/A-0/1 00:00:00 tree/iteration-002/IsEven True done 3/3 N/A-0/1 00:00:00 tree/iteration-002/even_task 8 done 3/3 N/A-0/1 00:00:00 tree/iteration-002/result 8 done 3/3 N/A-0/1 00:00:00 tree/sequence-002 [5, 16, 8] done 2/2 N/A-0/1 00:00:00 tree/stop_iteration-002 False done 3/3 N/A-0/1 00:00:00 tree/iteration-003/IsEven True done 4/4 N/A-0/1 00:00:00 tree/iteration-003/even_task 4 done 4/4 N/A-0/1 00:00:00 tree/iteration-003/result 4 done 4/4 N/A-0/1 00:00:00 tree/sequence-003 [5, 16, 8, 4] done 3/3 N/A-0/1 00:00:00 tree/stop_iteration-003 False
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry:if: have done 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: have done 1/1 tree/iteration-001/odd_task jump: result result: have done 2/2 tree/iteration-001/result entry: have done 1/1 tree/sequence-001 if: have done 1/1 tree/stop_iteration-001 T=finish F=entry jump: entry entry:if: have done 2/2 tree/iteration-002/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 3/3 tree/iteration-002/even_task jump: result result: have done 3/3 tree/iteration-002/result entry: have done 3/3 tree/sequence-002 if: have done 2/2 tree/stop_iteration-002 T=finish F=entry jump: entry entry:if: have done 3/3 tree/iteration-003/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 4/4 tree/iteration-003/even_task jump: result result: have done 4/4 tree/iteration-003/result entry: have done 4/4 tree/sequence-003 if: have done 3/3 tree/stop_iteration-003 T=finish F=entry jump: entry entry: add new 3/? tree/iteration-004/result if: add new 4/4 tree/iteration-004/IsEven T=even_branch F=odd_branch entry: add new 4/5 tree/sequence-004 if: add new 3/4 tree/stop_iteration-004 T=finish F=entry state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False done 1/1 N/A-0/1 00:00:00 tree/iteration-001/odd_task 16 done 2/2 N/A-0/1 00:00:00 tree/iteration-001/result 16 done 1/1 N/A-0/1 00:00:00 tree/sequence-001 [5, 16] done 1/1 N/A-0/1 00:00:00 tree/stop_iteration-001 False done 2/2 N/A-0/1 00:00:00 tree/iteration-002/IsEven True done 3/3 N/A-0/1 00:00:00 tree/iteration-002/even_task 8 done 3/3 N/A-0/1 00:00:00 tree/iteration-002/result 8 done 3/3 N/A-0/1 00:00:00 tree/sequence-002 [5, 16, 8] done 2/2 N/A-0/1 00:00:00 tree/stop_iteration-002 False done 3/3 N/A-0/1 00:00:00 tree/iteration-003/IsEven True done 4/4 N/A-0/1 00:00:00 tree/iteration-003/even_task 4 done 4/4 N/A-0/1 00:00:00 tree/iteration-003/result 4 done 4/4 N/A-0/1 00:00:00 tree/sequence-003 [5, 16, 8, 4] done 3/3 N/A-0/1 00:00:00 tree/stop_iteration-003 False done 4/4 N/A-0/1 00:00:00 tree/iteration-004/IsEven True new 3/? tree/iteration-004/result ø new 4/5 tree/sequence-004 ø new 3/4 tree/stop_iteration-004 ø
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry:if: have done 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: have done 1/1 tree/iteration-001/odd_task jump: result result: have done 2/2 tree/iteration-001/result entry: have done 1/1 tree/sequence-001 if: have done 1/1 tree/stop_iteration-001 T=finish F=entry jump: entry entry:if: have done 2/2 tree/iteration-002/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 3/3 tree/iteration-002/even_task jump: result result: have done 3/3 tree/iteration-002/result entry: have done 3/3 tree/sequence-002 if: have done 2/2 tree/stop_iteration-002 T=finish F=entry jump: entry entry:if: have done 3/3 tree/iteration-003/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 4/4 tree/iteration-003/even_task jump: result result: have done 4/4 tree/iteration-003/result entry: have done 4/4 tree/sequence-003 if: have done 3/3 tree/stop_iteration-003 T=finish F=entry jump: entry entry:if: have done 4/4 tree/iteration-004/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: add new 5/5 tree/iteration-004/even_task jump: result result: update new 4/5 tree/iteration-004/result entry: update new 4/5 tree/sequence-004 if: update new 3/4 tree/stop_iteration-004 T=finish F=entry state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False done 1/1 N/A-0/1 00:00:00 tree/iteration-001/odd_task 16 done 2/2 N/A-0/1 00:00:00 tree/iteration-001/result 16 done 1/1 N/A-0/1 00:00:00 tree/sequence-001 [5, 16] done 1/1 N/A-0/1 00:00:00 tree/stop_iteration-001 False done 2/2 N/A-0/1 00:00:00 tree/iteration-002/IsEven True done 3/3 N/A-0/1 00:00:00 tree/iteration-002/even_task 8 done 3/3 N/A-0/1 00:00:00 tree/iteration-002/result 8 done 3/3 N/A-0/1 00:00:00 tree/sequence-002 [5, 16, 8] done 2/2 N/A-0/1 00:00:00 tree/stop_iteration-002 False done 3/3 N/A-0/1 00:00:00 tree/iteration-003/IsEven True done 4/4 N/A-0/1 00:00:00 tree/iteration-003/even_task 4 done 4/4 N/A-0/1 00:00:00 tree/iteration-003/result 4 done 4/4 N/A-0/1 00:00:00 tree/sequence-003 [5, 16, 8, 4] done 3/3 N/A-0/1 00:00:00 tree/stop_iteration-003 False done 4/4 N/A-0/1 00:00:00 tree/iteration-004/IsEven True done 5/5 N/A-0/1 00:00:00 tree/iteration-004/even_task 2 done 5/5 N/A-0/1 00:00:00 tree/iteration-004/result 2 done 5/5 N/A-0/1 00:00:00 tree/sequence-004 [5, 16, 8, 4, 2] done 4/4 N/A-0/1 00:00:00 tree/stop_iteration-004 False
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry:if: have done 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: have done 1/1 tree/iteration-001/odd_task jump: result result: have done 2/2 tree/iteration-001/result entry: have done 1/1 tree/sequence-001 if: have done 1/1 tree/stop_iteration-001 T=finish F=entry jump: entry entry:if: have done 2/2 tree/iteration-002/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 3/3 tree/iteration-002/even_task jump: result result: have done 3/3 tree/iteration-002/result entry: have done 3/3 tree/sequence-002 if: have done 2/2 tree/stop_iteration-002 T=finish F=entry jump: entry entry:if: have done 3/3 tree/iteration-003/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 4/4 tree/iteration-003/even_task jump: result result: have done 4/4 tree/iteration-003/result entry: have done 4/4 tree/sequence-003 if: have done 3/3 tree/stop_iteration-003 T=finish F=entry jump: entry entry:if: have done 4/4 tree/iteration-004/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 5/5 tree/iteration-004/even_task jump: result result: have done 5/5 tree/iteration-004/result entry: have done 5/5 tree/sequence-004 if: have done 4/4 tree/stop_iteration-004 T=finish F=entry jump: entry entry: add new 4/? tree/iteration-005/result if: add new 5/5 tree/iteration-005/IsEven T=even_branch F=odd_branch entry: add new 5/6 tree/sequence-005 if: add new 4/5 tree/stop_iteration-005 T=finish F=entry state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False done 1/1 N/A-0/1 00:00:00 tree/iteration-001/odd_task 16 done 2/2 N/A-0/1 00:00:00 tree/iteration-001/result 16 done 1/1 N/A-0/1 00:00:00 tree/sequence-001 [5, 16] done 1/1 N/A-0/1 00:00:00 tree/stop_iteration-001 False done 2/2 N/A-0/1 00:00:00 tree/iteration-002/IsEven True done 3/3 N/A-0/1 00:00:00 tree/iteration-002/even_task 8 done 3/3 N/A-0/1 00:00:00 tree/iteration-002/result 8 done 3/3 N/A-0/1 00:00:00 tree/sequence-002 [5, 16, 8] done 2/2 N/A-0/1 00:00:00 tree/stop_iteration-002 False done 3/3 N/A-0/1 00:00:00 tree/iteration-003/IsEven True done 4/4 N/A-0/1 00:00:00 tree/iteration-003/even_task 4 done 4/4 N/A-0/1 00:00:00 tree/iteration-003/result 4 done 4/4 N/A-0/1 00:00:00 tree/sequence-003 [5, 16, 8, 4] done 3/3 N/A-0/1 00:00:00 tree/stop_iteration-003 False done 4/4 N/A-0/1 00:00:00 tree/iteration-004/IsEven True done 5/5 N/A-0/1 00:00:00 tree/iteration-004/even_task 2 done 5/5 N/A-0/1 00:00:00 tree/iteration-004/result 2 done 5/5 N/A-0/1 00:00:00 tree/sequence-004 [5, 16, 8, 4, 2] done 4/4 N/A-0/1 00:00:00 tree/stop_iteration-004 False done 5/5 N/A-0/1 00:00:00 tree/iteration-005/IsEven True new 4/? tree/iteration-005/result ø new 5/6 tree/sequence-005 ø new 4/5 tree/stop_iteration-005 ø
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry:if: have done 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: have done 1/1 tree/iteration-001/odd_task jump: result result: have done 2/2 tree/iteration-001/result entry: have done 1/1 tree/sequence-001 if: have done 1/1 tree/stop_iteration-001 T=finish F=entry jump: entry entry:if: have done 2/2 tree/iteration-002/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 3/3 tree/iteration-002/even_task jump: result result: have done 3/3 tree/iteration-002/result entry: have done 3/3 tree/sequence-002 if: have done 2/2 tree/stop_iteration-002 T=finish F=entry jump: entry entry:if: have done 3/3 tree/iteration-003/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 4/4 tree/iteration-003/even_task jump: result result: have done 4/4 tree/iteration-003/result entry: have done 4/4 tree/sequence-003 if: have done 3/3 tree/stop_iteration-003 T=finish F=entry jump: entry entry:if: have done 4/4 tree/iteration-004/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 5/5 tree/iteration-004/even_task jump: result result: have done 5/5 tree/iteration-004/result entry: have done 5/5 tree/sequence-004 if: have done 4/4 tree/stop_iteration-004 T=finish F=entry jump: entry entry:if: have done 5/5 tree/iteration-005/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: add new 6/6 tree/iteration-005/even_task jump: result result: update new 5/6 tree/iteration-005/result entry: update new 5/6 tree/sequence-005 if: update new 4/5 tree/stop_iteration-005 T=finish F=entry state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False done 1/1 N/A-0/1 00:00:00 tree/iteration-001/odd_task 16 done 2/2 N/A-0/1 00:00:00 tree/iteration-001/result 16 done 1/1 N/A-0/1 00:00:00 tree/sequence-001 [5, 16] done 1/1 N/A-0/1 00:00:00 tree/stop_iteration-001 False done 2/2 N/A-0/1 00:00:00 tree/iteration-002/IsEven True done 3/3 N/A-0/1 00:00:00 tree/iteration-002/even_task 8 done 3/3 N/A-0/1 00:00:00 tree/iteration-002/result 8 done 3/3 N/A-0/1 00:00:00 tree/sequence-002 [5, 16, 8] done 2/2 N/A-0/1 00:00:00 tree/stop_iteration-002 False done 3/3 N/A-0/1 00:00:00 tree/iteration-003/IsEven True done 4/4 N/A-0/1 00:00:00 tree/iteration-003/even_task 4 done 4/4 N/A-0/1 00:00:00 tree/iteration-003/result 4 done 4/4 N/A-0/1 00:00:00 tree/sequence-003 [5, 16, 8, 4] done 3/3 N/A-0/1 00:00:00 tree/stop_iteration-003 False done 4/4 N/A-0/1 00:00:00 tree/iteration-004/IsEven True done 5/5 N/A-0/1 00:00:00 tree/iteration-004/even_task 2 done 5/5 N/A-0/1 00:00:00 tree/iteration-004/result 2 done 5/5 N/A-0/1 00:00:00 tree/sequence-004 [5, 16, 8, 4, 2] done 4/4 N/A-0/1 00:00:00 tree/stop_iteration-004 False done 5/5 N/A-0/1 00:00:00 tree/iteration-005/IsEven True done 6/6 N/A-0/1 00:00:00 tree/iteration-005/even_task 1 done 6/6 N/A-0/1 00:00:00 tree/iteration-005/result 1 done 6/6 N/A-0/1 00:00:00 tree/sequence-005 [5, 16, 8, 4, 2, 1] done 5/5 N/A-0/1 00:00:00 tree/stop_iteration-005 True
$ tb workflow collatz2_workflow.py && tb run . > /dev/null && tb ls -csirITfo --sort=topo entry:if: have done 0/0 tree/iteration-001/IsEven T=even_branch F=odd_branch jump: odd_branch odd_branch: have done 1/1 tree/iteration-001/odd_task jump: result result: have done 2/2 tree/iteration-001/result entry: have done 1/1 tree/sequence-001 if: have done 1/1 tree/stop_iteration-001 T=finish F=entry jump: entry entry:if: have done 2/2 tree/iteration-002/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 3/3 tree/iteration-002/even_task jump: result result: have done 3/3 tree/iteration-002/result entry: have done 3/3 tree/sequence-002 if: have done 2/2 tree/stop_iteration-002 T=finish F=entry jump: entry entry:if: have done 3/3 tree/iteration-003/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 4/4 tree/iteration-003/even_task jump: result result: have done 4/4 tree/iteration-003/result entry: have done 4/4 tree/sequence-003 if: have done 3/3 tree/stop_iteration-003 T=finish F=entry jump: entry entry:if: have done 4/4 tree/iteration-004/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 5/5 tree/iteration-004/even_task jump: result result: have done 5/5 tree/iteration-004/result entry: have done 5/5 tree/sequence-004 if: have done 4/4 tree/stop_iteration-004 T=finish F=entry jump: entry entry:if: have done 5/5 tree/iteration-005/IsEven T=even_branch F=odd_branch jump: even_branch even_branch: have done 6/6 tree/iteration-005/even_task jump: result result: have done 6/6 tree/iteration-005/result entry: have done 6/6 tree/sequence-005 if: have done 5/5 tree/stop_iteration-005 T=finish F=entry jump: finish finish: add new 6/6 tree/result state info tags worker time folder output ──────── ────────── ─────────── ─────────── ─────────── ───────────────────────────── ──────────────────────── done 0/0 N/A-0/1 00:00:00 tree/iteration-001/IsEven False done 1/1 N/A-0/1 00:00:00 tree/iteration-001/odd_task 16 done 2/2 N/A-0/1 00:00:00 tree/iteration-001/result 16 done 1/1 N/A-0/1 00:00:00 tree/sequence-001 [5, 16] done 1/1 N/A-0/1 00:00:00 tree/stop_iteration-001 False done 2/2 N/A-0/1 00:00:00 tree/iteration-002/IsEven True done 3/3 N/A-0/1 00:00:00 tree/iteration-002/even_task 8 done 3/3 N/A-0/1 00:00:00 tree/iteration-002/result 8 done 3/3 N/A-0/1 00:00:00 tree/sequence-002 [5, 16, 8] done 2/2 N/A-0/1 00:00:00 tree/stop_iteration-002 False done 3/3 N/A-0/1 00:00:00 tree/iteration-003/IsEven True done 4/4 N/A-0/1 00:00:00 tree/iteration-003/even_task 4 done 4/4 N/A-0/1 00:00:00 tree/iteration-003/result 4 done 4/4 N/A-0/1 00:00:00 tree/sequence-003 [5, 16, 8, 4] done 3/3 N/A-0/1 00:00:00 tree/stop_iteration-003 False done 4/4 N/A-0/1 00:00:00 tree/iteration-004/IsEven True done 5/5 N/A-0/1 00:00:00 tree/iteration-004/even_task 2 done 5/5 N/A-0/1 00:00:00 tree/iteration-004/result 2 done 5/5 N/A-0/1 00:00:00 tree/sequence-004 [5, 16, 8, 4, 2] done 4/4 N/A-0/1 00:00:00 tree/stop_iteration-004 False done 5/5 N/A-0/1 00:00:00 tree/iteration-005/IsEven True done 6/6 N/A-0/1 00:00:00 tree/iteration-005/even_task 1 done 6/6 N/A-0/1 00:00:00 tree/iteration-005/result 1 done 6/6 N/A-0/1 00:00:00 tree/sequence-005 [5, 16, 8, 4, 2, 1] done 5/5 N/A-0/1 00:00:00 tree/stop_iteration-005 True done 6/6 N/A-0/1 00:00:00 tree/result [5, 16, 8, 4, 2, 1]
Note how each iteration of the while loop is run seperately, since it involves resolving an if statement. The loop keyword to the branch decorator automatically numbers the folders so that each iteration of the branch is given a specific folder. Finally the result points to the sequence calculated in the final iteration.
$ tb view tree/result
name: result
location: /home/myuser/tmprepo/tree/result
state: done
target: define(…)
wait for: 0 dependencies
depth: 20
source workflow: <root workflow>
frozen by: (not frozen)
latest handled inputs:
None
handlers:
[]
handler data:
<None>
parents:
sequence-005
stop_iteration-001
stop_iteration-002
stop_iteration-003
stop_iteration-004
stop_iteration-005
input:
["define", {"__tb_implicit_remove__": [["stop_iteration-001", {"__tb_type__": "ref", "index": [], "name": "stop_iteration-001"}], ["stop_iteration-002", {"__tb_type__": "ref", "index": [], "name": "stop_iteration-002"}], ["stop_iteration-003", {"__tb_type__": "ref", "index": [], "name": "stop_iteration-003"}], ["stop_iteration-004", {"__tb_type__": "ref", "index": [], "name": "stop_iteration-004"}], ["stop_iteration-005", {"__tb_type__": "ref", "index": [], "name": "stop_iteration-005"}]], "obj": {"__tb_type__": "ref", "index": [], "name": "sequence-005"}}]
output:
[5, 16, 8, 4, 2, 1]
Run information:
Worker name: N/A-0/1
Start time: 2025-03-17 10:08:35
End time: 2025-03-17 10:08:35
Duration: 0:00:00
Error: None
No custom actions defined for this task.
The graphical representation of the workflow is given here
.
Note, that due to experimental nature of the automatic workflow visualizer, some variable arrows are missing,
but the branching arrows are all there.