Graph inputs and outputs¶
To create this graph we need only to install the tierkreis
package:
pip install tierkreis
Graphs¶
Single input and single output¶
Elementary types¶
Like Python functions, Tierkreis graphs can have input and output arguments.
We use the constructor of GraphBuilder
to indicate that our function takes a single integer to a single integer:
from tierkreis.builder import GraphBuilder
from tierkreis.models import TKR
# f(x) = 2x + 1
f = GraphBuilder(TKR[int], TKR[int])
The implementation of this graph can be done entirely using Tierkreis built-in functions:
from tierkreis.builtins.stubs import iadd, itimes
double = f.task(itimes(f.const(2), f.inputs))
f_out = f.task(iadd(double, f.const(1)))
f.outputs(f_out)
Nested types within a single output¶
Sometimes we want to return a nested data structure within a single output.
To do this we define a Python NamedTuple
or pydantic.BaseModel
.
from typing import NamedTuple
class FibDataStruct(NamedTuple):
a: int
b: int
## Alternative using pydantic.BaseModel
# from pydantic import BaseModel
# class FibDataStruct(BaseModel):
# a: int
# b: int
To use this as part of the signature of a graph, we wrap it in TKR
.
The TKR[A]
wrapper type indicates that a single input/output contains a value of type A
.
The contents of A
will not in general be accessible to the graph builder code.
from tierkreis.models import EmptyModel
init_data = GraphBuilder(EmptyModel, TKR[FibDataStruct])
init_data.outputs(init_data.const(FibDataStruct(a=0, b=1)))
Multiple inputs and multiple outputs¶
However a Tierkreis graph can also have multiple inputs and multiple outputs.
To indicate that more than one input/output is required we again use a NamedTuple
,
except this time one whose attributes are all Tierkreis types (i.e. wrapped in TKR
).
class FibData(NamedTuple):
a: TKR[int]
b: TKR[int]
To use this in the signature of a graph, we pass it directly in.
This way Tierkreis will interpret the different attributes of the NamedTuple
as different inputs/outputs.
from tierkreis.builder import GraphBuilder
from tierkreis.builtins.stubs import iadd
from tierkreis.models import TKR
fib_step = GraphBuilder(FibData, FibData)
sum = fib_step.task(iadd(fib_step.inputs.a, fib_step.inputs.b))
fib_step.outputs(FibData(fib_step.inputs.b, sum))
Note that we are now able to access the contents of FibData
in the graph builder.
Note
What would happen if we used a nested data structure inside a single input/output to construct this graph?
If instead we wanted to have a single output containing a nested structure FibData
then we would initialize the graph builder as follows:
class FibData(NamedTuple):
a: int
b: int
fib_step_2 = GraphBuilder(TKR[FibData], TKR[FibData])
However we would then not be able to access attributes of FibData
in the graph builder code.
# type error: 'TKR' object has no attribute 'a'
sum = fib_step_2.task(iadd(fib_step_2.inputs.a, fib_step_2.inputs.b))
Hint
We can use the different behavior of the above two examples to create a separation of concerns between the graph builder and the workers. If some data is required in graph builder code then we use multiple inputs/outputs. If some data is only used in workers and can be passed between them without the graph needing to inspect them then we use a single input/output containing within it a nested data structure.
Combinations of single and multiple inputs¶
We can combine the various types of inputs and outputs in the natural way.
For instance the following are all valid ways to construct a GraphBuilder
object:
class MultiPortInputData(NamedTuple):
a: TKR[int]
b: TKR[str]
class MultiPortOutputData(NamedTuple):
a: TKR[str]
b: TKR[list[int]]
g = GraphBuilder(TKR[int], TKR[str])
g = GraphBuilder(MultiPortInputData, MultiPortOutputData)
g = GraphBuilder(TKR[str], MultiPortOutputData)
g = GraphBuilder(MultiPortInputData, TKR[str])
Execution¶
Since we still only use built-in functions, we execute the graph in the same way as before.
For the examples with graph inputs, we provide the input in the third argument of run_graph
.
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="Graph inputs and outputs")
executor = ShellExecutor(Path("."), logs_path=storage.logs_path)
storage.clean_graph_files()
run_graph(storage, executor, f.get_data(), 10)
print(read_outputs(storage))
storage.clean_graph_files()
run_graph(storage, executor, init_data.get_data(), {})
print(read_outputs(storage))
storage.clean_graph_files()
run_graph(storage, executor, fib_step.get_data(), {'a': 0, 'b': 1})
print(read_outputs(storage))
21
[0, 1]
{'a': 1, 'b': 1}