pralin/compose
pralin/compose allows to define combination of algorithms according to a YAML specification.
The root of a valid pralin/compose file is a dictionnary with a single key compose. For example, the following compute 1+a and output the result:
compose:
inputs:
- a
outputs:
- add[0]
process:
- pralin/arithmetic/addition:
id: add
inputs: [1, a]
Expressions
pralin/compose allows to define values as integers, floating points or strings.
strings and references
Strings are either interpreted as a string or a reference to an output. Reference to outputs take the form ìd[number] where ìd reference a node in the graph, and number is the index of its output.
If it is required to define a string of the form ìd[number] and to use it as a string. Then it needs to be tagged with !!str.
In the following example, str1 and str2 are interprated as strings, while str3 is interprated as output index 1 of operation op:
str1: "op[1"
str2: !!str "op[1]"
str3: "op[1]"
Other types of references are inputs to the graph, they can take an arbitrary identifier, and care should be taken to avoid confusion with string values.
sequence
By default, a sequence of value is interpreted as a list of references to other states. The actual value is the value of the last modified referenced state.
In the following example, if neither op0 nor op1 has been executed, then the value is 1, otherwise it is set to the corresponding output of the last executed nodes between op0 and op1.
[1, "op0[1]", "op1[2]"]
It is possible to create a value as a state, then it is necesserary to prefix it with !!seq, the following creates a list with 3 values: 1, the last value from output 1 of op0 and the last value from output 2 of op1:
!!seq [1, "op0[1]", "op1[2]"]
map
By default a map is triggered as an error, but similarly to sequences, if they are prefixed with !!map they can be used to create an any_value_map, using:
!!map { a: 1, b: "op0[1]", c: "op1[2]" }
More complex maps can be created:
!!map { a: 1, b: !!seq ["op0[1]", 3 ], c: !!map { d: "op1[2]", e: "op0[0]" } }
parameters
It is possible to define parameters given as argument to the graph. They can be used by using the tag !param followed by the name. For instance:
!param name_of_parameter
When using the !param tag, it is recommended to define the tag namespace at the top of the YAML file with:
%TAG ! tag:cyloncore.com/pralin,2023:
compose
compose is the root node of the pralin/compose computation graph. It is defined by a dicitonnary with the following keys: ìnputs, outputs, process, variables.
The following examples shows a graph, with one node adding 1 to an input value.
compose:
inputs:
- value
outputs:
- add[0]
process:
- pralin/arithmetic/addition:
id: add
inputs: [value, 1]
inputs
inputs should be an array of name for each inputs.
outputs
outputs should be an array of values or a dictionnary for naming outputs.
process
process is an array of dictionnaries defining the computation nodes, see bellow for a full description of the different nodes
variables
variables allow to give a name to values that are used in different places in the computation graph. They are implemnted using YAML references. They need to be defined in variables element of the compose dictionnary, by adding a tag starting with & (for instance, &tagname). They can then be used by referencing the tag with * (for instance, *tagname).
The following example defines a variable called counter which is set to either 0 or the index 0 of add outputs. The variable is then used to set the result output:
variables:
counter: &counter [0, 'add[0]']
outputs:
result: *counter
parameters
It is possible to define global parameters in the graph, with default value. It is also possible to define parameters that needs to be defined externaly to the graph, using the !required tag, as follow:
parameters:
default_value: 1
required_parameter: !required null
In this case, if the default_value is not redefined it will be set to 1. However, if required_parameter is not set in the computation graph, this should trigger an error.
Nodes
This sections conver the different processing nodes
computation node
A computation node in the graph corresponds to a \ref pralin::algorithm_instance and executes its computations.
In a process, this node is initiated by a key of the shape module/algorithm_name where module is the name of the library where the àlgorithm_name is defined (the same as what is used for lookup in algorithms_registry).
It supports the following elements:
ida string used to identify the node, in particular to use its outputs as inputs for a different nodeìnputsan array defining the input value from the computationparametersa dictionnary defining the arguments for the computation node
The following example defines a computation node for the addition algorithm from the artithmetic module, it will result in the addition of the constant 4 with the index 0 output of other_op:
arithmetic/addition:
id: add_1
inputs: [4, 'other_op[0]']
The following example defines a computation node for the test_parameters algorithm from the test_ops module. It takes three parameters as aruments integer, floating_point and string. Note the tuse of !!str should be interpreted as a string, and not a reference to output index 2 of tp:
test_ops/test_parameters:
id: tp
parameters:
integer: 45
floating_point: 82.0
string: !!str "tp[2]"
Sometimnes an input can take multiple expressions, then the expression is represented as an array. For instance, the following can be used to define an incremental counter:
arithmetic/addition:
id: inc
inputs: [[0, 'inc[0]'], 1]
In this example, the first input of the addition is set to 0 or inc[0]. In practice, the input is set to the last computed value. In this case, it is initialised to 0 and then update with the result of the increment operation.
For complex type that cannot be represented in yaml, it is possible to use the default keyword, for instance:
complex/recusrive/operation:
id: op
inputs: [[default, 'op[0]']]
conditional
conditional executes a set of operations if a condition is true. As show in the exmaple bellow:
conditional:
condition: [true, 'inf_1[0]']
process:
- pralin/arithmetic/addition:
id: add_1
inputs: [ *counter_1, 1]
conditional has an alternative form where it test the equality between two values, defined by on and equals. In the following example add_0 is only exxecuted if mode == "mode_1":
conditional:
on: mode
equals: "mode_1"
process:
- pralin/arithmetic/addition:
id: add_0
inputs: [1, 2]
repeat_while
repeat_while repeat the execution of the nodes defined in process until the condition is false.
repeat_while:
condition: [true, 'inf[0]']
process:
- pralin/arithmetic/addition:
id: add
inputs: [ *counter, 1]
- pralin/arithmetic/inferior:
id: inf
inputs: [ *counter, 5]
parallel
parallel allows the execution of children nodes defined in process in different threads. The implementation uses a tasks queue, with a limited number of jobs (default is set by the number of cores on the computer). pralin does not handle automatically thread starvation, it is up to the user to make sure this does not happen. In particular, while it is possible to nest parallel inside an other parallel block, this could lead to dead locks, as the parent parallel threads is not released until the children are done.
Example of use:
parallel:
process:
- pralin/arithmetic/inferior:
id: inf_1
inputs: [ *counter_1, end_value_1]
- pralin/arithmetic/inferior:
id: inf_2
inputs: [ *counter_2, end_value_2]
The two operations inf_1 and inf_2 are executed in different thread.
sequence
sequence allows the executions of children in sequence. Most constructions blocks (repeat_while, conditional….) default to using a sequence internally.
Example of use:
sequence:
process:
- pralin/arithmetic/inferior:
id: inf_1
inputs: [ *counter_1, end_value_1]
- pralin/arithmetic/inferior:
id: inf_2
inputs: [ *counter_2, end_value_2]
template
template is used to create strings by formatting different values from the composition graph.
Example of use:
compose:
inputs:
- input_value
outputs:
result: "st[0]"
parameters:
param_0: "h: "
process:
- pralin/arithmetic/addition:
id: add_0
inputs: [1, "input_value"]
- template:
id: st
template: "{}{}->{}\n"
inputs: [!param param_0, "input_value", "add_0[0]"]