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:
id
a string used to identify the node, in particular to use its outputs as inputs for a different nodeìnputs
an array defining the input value from the computationparameters
a 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]"]