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:

  1. If the number is even, divide by two

  2. 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.