Iteration using Loop¶
One way to perform iteration in Tierkreis is to use GraphBuilder.loop
.
The first argument to GraphBuilder.loop
is a graph that constitutes the loop body.
The second is the initial input to the loop.
Graphs¶
Bounded iteration¶
As an example to introduce the concepts, we create a bounded iteration that has additional inputs to configure the iteration. The signature of the loop body is:
from typing import NamedTuple
from tierkreis.builder import GraphBuilder
from tierkreis.models import TKR
class LoopBodyInput(NamedTuple):
i: TKR[int]
value: TKR[int]
step: TKR[int]
bound: TKR[int]
class LoopBodyOutput(NamedTuple):
i: TKR[int]
value: TKR[int]
should_continue: TKR[bool]
loop_body = GraphBuilder(LoopBodyInput, LoopBodyOutput)
The loop body input type consists of all the variables that we want the loop body to have access to.
This includes variables that the loop does not modify, like step
and bound
.
The loop body output type consists of the variables that the loop modifies
(in this case the counter i
and the value
)
plus a special boolean output called should_continue
that tells the loop when to finish.
In our example the loop body increments the counter i
,
adds step
to the value
and checks if i
has met the bound.
from tierkreis.builtins.stubs import iadd, igt, rand_int
i = loop_body.task(iadd(loop_body.const(1), loop_body.inputs.i))
value = loop_body.task(iadd(loop_body.inputs.step, loop_body.inputs.value))
pred = loop_body.task(igt(loop_body.inputs.bound, i))
loop_body.outputs(LoopBodyOutput(i=i, value=value, should_continue=pred))
The main graph constructs the initial values for the loop and uses GraphBuilder.loop
to run the loop.
from tierkreis.models import EmptyModel
f = GraphBuilder(EmptyModel, TKR[int])
init = LoopBodyInput(f.const(0), f.const(0), f.const(2), f.const(10))
loop_output = f.loop(loop_body, init)
f.outputs(loop_output.value)
Repeat until success¶
In addition to bounded iteration, the GraphBuilder.loop
method can also define a ‘repeat until success’ loop.
First we need to create the graph that constitutes the body of the loop.
As a toy example, we choose a random number between 1 and 10 inclusive and continue if it is less than 10.
from typing import NamedTuple
from tierkreis.builder import GraphBuilder
from tierkreis.builtins.stubs import iadd, igt, rand_int
from tierkreis.models import TKR
class LoopBodyInput(NamedTuple):
i: TKR[int]
class LoopBodyOutput(NamedTuple):
i: TKR[int]
should_continue: TKR[bool]
body = GraphBuilder(LoopBodyInput, LoopBodyOutput)
i = body.task(iadd(body.const(1), body.inputs.i))
a = body.task(rand_int(body.const(0), body.const(10)))
pred = body.task(igt(body.const(10), a))
body.outputs(LoopBodyOutput(i=i, should_continue=pred))
The main graph runs the loop and tells us the iteration on which we found success.
from tierkreis.models import EmptyModel
g = GraphBuilder(EmptyModel, TKR[int])
loop_output = g.loop(body, LoopBodyInput(g.const(0)))
g.outputs(loop_output.i)
Execution¶
Since we still only use built-in functions, we execute the graph in the same way as before.
from uuid import UUID
from pathlib import Path
from tierkreis import run_graph
from tierkreis.storage import FileStorage, read_outputs
from tierkreis.executor import ShellExecutor
storage = FileStorage(UUID(int=99), name="Nested graphs using Eval")
executor = ShellExecutor(Path("."), logs_path=storage.logs_path)
storage.clean_graph_files()
run_graph(storage, executor, f.get_data(), {})
print(read_outputs(storage))
storage.clean_graph_files()
run_graph(storage, executor, g.get_data(), {})
print(read_outputs(storage))
20
8