5. Sum and product: transformations with inputs¶
Now let us switch to the transformations with inputs. Transformation with inputs is in a sense a function with arguments. We are still working with transformations that do not depend on variables, therefore the result of the following transformations will be computed only once.
5.1. Sum¶
The Sum transformation is used to do elementwise sum of the arrays and histograms. See the following code:
1import gna.constructors as C
2import numpy as np
3# Create several points instances
4n, n1, n2 = 12, 3, 4
5points_list = [ C.Points(np.arange(i, i+n).reshape(n1, n2)) for i in range(5) ]
6
7# Create a sum instance
8tsum = R.Sum()
9
10for i, p in enumerate(points_list):
11 out = p.points.points
12 tsum.add(out)
13
14 print('Input %i:'%i)
15 print(out.data(), end='\n\n')
16
17# Print the structure of Sum object
18tsum.print()
19print()
20
21
22# Access the calcuation result:
23print('Sum result:')
24print(tsum.sum.sum.data())
The Sum
object is created without arguments:
tsum = R.Sum()
It may have some inputs. Each input is binded with method add(output)
, as it is done in:
tsum.add(out)
The printout of the Sum
instance now contains list of all bound inputs:
[obj] Sum: 1 transformation(s)
0 [trans] sum: 5 input(s), 1 output(s)
0 [in] points -> [out] points: array 2d, shape 3x4, size 12
1 [in] points -> [out] points: array 2d, shape 3x4, size 12
2 [in] points -> [out] points: array 2d, shape 3x4, size 12
3 [in] points -> [out] points: array 2d, shape 3x4, size 12
4 [in] points -> [out] points: array 2d, shape 3x4, size 12
0 [out] sum: array 2d, shape 3x4, size 12
The outputs should be of the same type and shape. The code produces the following output:
Input 0:
[[ 0. 1. 2. 3.]
[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]]
Input 1:
[[ 1. 2. 3. 4.]
[ 5. 6. 7. 8.]
[ 9. 10. 11. 12.]]
Input 2:
[[ 2. 3. 4. 5.]
[ 6. 7. 8. 9.]
[10. 11. 12. 13.]]
Input 3:
[[ 3. 4. 5. 6.]
[ 7. 8. 9. 10.]
[11. 12. 13. 14.]]
Input 4:
[[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]
[12. 13. 14. 15.]]
[obj] Sum: 1 transformation(s)
0 [trans] sum: 5 input(s), 1 output(s)
0 [in] points -> [out] points: array 2d, shape 3x4, size 12
1 [in] points -> [out] points: array 2d, shape 3x4, size 12
2 [in] points -> [out] points: array 2d, shape 3x4, size 12
3 [in] points -> [out] points: array 2d, shape 3x4, size 12
4 [in] points -> [out] points: array 2d, shape 3x4, size 12
0 [out] sum: array 2d, shape 3x4, size 12
Sum result:
[[10. 15. 20. 25.]
[30. 35. 40. 45.]
[50. 55. 60. 65.]]
5.2. Ways to construct Sum and add inputs¶
There are several ways to add inputs to the Sum
instance:
Construct
Sum
passing list of outputs as an argument.Construct empty
Sum
and add each output manually viaadd()
method.Construct empty
Sum
, create a set of named inputs viaadd_input()
method and bind them manually.
The second method was shown in the example above:
tsum.add(out)
All of the methods in comparison are presented in the following example:
1import gna.constructors as C
2import numpy as np
3# Create several points instances
4n, n1, n2 = 12, 3, 4
5points_list = [ C.Points(np.arange(i, i+n).reshape(n1, n2)) for i in range(5) ]
6
7# Create sum instances
8tsum_constructor = C.Sum([p.points.points for p in points_list])
9tsum_add = R.Sum()
10tsum_add_input = R.Sum()
11
12for i, p in enumerate(points_list):
13 out = p.points.points
14 tsum_add.add(out)
15
16 an_input = tsum_add_input.add_input('input_{:02d}'.format(i))
17 an_input(out)
18
19# Print the structure of Sum object
20print('Sum, configured via constructor')
21tsum_constructor.print()
22print()
23
24# Print the structure of Sum object
25print('Sum, configured via add() method')
26tsum_add.print()
27print()
28
29# Print the structure of Sum object
30print('Sum, configured via add_input() method')
31tsum_add_input.print()
32print()
33
34
35# Access the calcuation result:
36print('Results:')
37print(tsum_constructor.sum.sum.data())
38print()
39print(tsum_add.sum.sum.data())
40print()
41print(tsum_add_input.sum.sum.data())
First method is shown on line 8: the Sum
constructor from constructors
module may have a list of outputs as an
argument.
tsum_constructor = C.Sum([p.points.points for p in points_list])
The second method, the one shown in the previous example, is represented by lines 9 and 14:
tsum_add = R.Sum()
for i, p in enumerate(points_list):
out = p.points.points
tsum_add.add(out)
The third method is very close to the second one. It is represented by lines 10, 16 and 17:
tsum_add_input = R.Sum()
for i, p in enumerate(points_list):
out = p.points.points
an_input = tsum_add_input.add_input('input_{:02d}'.format(i))
an_input(out)
The binding is done in two steps. At first we create a named input:
an_input = tsum_add_input.add_input('input_{:02d}'.format(i))
The input then is connected via call:
an_input(out)
The example script prints the inputs and outputs for each case:
Sum, configured via constructor
[obj] Sum: 1 transformation(s)
0 [trans] sum: 5 input(s), 1 output(s)
0 [in] points -> [out] points: array 2d, shape 3x4, size 12
1 [in] points -> [out] points: array 2d, shape 3x4, size 12
2 [in] points -> [out] points: array 2d, shape 3x4, size 12
3 [in] points -> [out] points: array 2d, shape 3x4, size 12
4 [in] points -> [out] points: array 2d, shape 3x4, size 12
0 [out] sum: array 2d, shape 3x4, size 12
Sum, configured via add() method
[obj] Sum: 1 transformation(s)
0 [trans] sum: 5 input(s), 1 output(s)
0 [in] points -> [out] points: array 2d, shape 3x4, size 12
1 [in] points -> [out] points: array 2d, shape 3x4, size 12
2 [in] points -> [out] points: array 2d, shape 3x4, size 12
3 [in] points -> [out] points: array 2d, shape 3x4, size 12
4 [in] points -> [out] points: array 2d, shape 3x4, size 12
0 [out] sum: array 2d, shape 3x4, size 12
Sum, configured via add_input() method
[obj] Sum: 1 transformation(s)
0 [trans] sum: 5 input(s), 1 output(s)
0 [in] input_00 -> [out] points: array 2d, shape 3x4, size 12
1 [in] input_01 -> [out] points: array 2d, shape 3x4, size 12
2 [in] input_02 -> [out] points: array 2d, shape 3x4, size 12
3 [in] input_03 -> [out] points: array 2d, shape 3x4, size 12
4 [in] input_04 -> [out] points: array 2d, shape 3x4, size 12
0 [out] sum: array 2d, shape 3x4, size 12
Results:
[[10. 15. 20. 25.]
[30. 35. 40. 45.]
[50. 55. 60. 65.]]
[[10. 15. 20. 25.]
[30. 35. 40. 45.]
[50. 55. 60. 65.]]
[[10. 15. 20. 25.]
[30. 35. 40. 45.]
[50. 55. 60. 65.]]
See that in case three names of the inputs are defined as user has specified.
5.3. Product¶
The Product
transformation is very similar to the Sum
. The example, one to one similar to the example from the
previous section may be found below. The main differences are that there is a method multiply(output)
for binding
outputs. The object defines transformation product with output, named product.
1import gna.constructors as C
2import numpy as np
3# Create several points instances
4n, n1, n2 = 12, 3, 4
5points_list = [ C.Points(np.arange(i, i+n).reshape(n1, n2)) for i in range(5) ]
6
7# Create product instances
8tproduct_constructor = C.Product([p.points.points for p in points_list])
9tproduct_multiply = C.Product()
10tproduct_add_input = C.Product()
11
12for i, p in enumerate(points_list):
13 out = p.points.points
14 tproduct_multiply.multiply(out)
15
16 an_input = tproduct_add_input.add_input('input_{:02d}'.format(i))
17 an_input(out)
18
19# Print the structure of Product object
20print('Product, configured via constructor')
21tproduct_constructor.print()
22print()
23
24# Print the structure of Product object
25print('Product, configured via multiply() method')
26tproduct_multiply.print()
27print()
28
29# Print the structure of Product object
30print('Product, configured via add_input() method')
31tproduct_add_input.print()
32print()
33
34
35# Access the calcuation result:
36print('Results:')
37print(tproduct_constructor.product.product.data())
38print()
39print(tproduct_multiply.product.product.data())
40print()
41print(tproduct_add_input.product.product.data())
42
The script produces the following output. Check it and compare with the output of the Sum
example from the previous
section.
Product, configured via constructor
[obj] Product: 1 transformation(s)
0 [trans] product: 5 input(s), 1 output(s)
0 [in] points -> [out] points: array 2d, shape 3x4, size 12
1 [in] points -> [out] points: array 2d, shape 3x4, size 12
2 [in] points -> [out] points: array 2d, shape 3x4, size 12
3 [in] points -> [out] points: array 2d, shape 3x4, size 12
4 [in] points -> [out] points: array 2d, shape 3x4, size 12
0 [out] product: array 2d, shape 3x4, size 12
Product, configured via multiply() method
[obj] Product: 1 transformation(s)
0 [trans] product: 5 input(s), 1 output(s)
0 [in] points -> [out] points: array 2d, shape 3x4, size 12
1 [in] points -> [out] points: array 2d, shape 3x4, size 12
2 [in] points -> [out] points: array 2d, shape 3x4, size 12
3 [in] points -> [out] points: array 2d, shape 3x4, size 12
4 [in] points -> [out] points: array 2d, shape 3x4, size 12
0 [out] product: array 2d, shape 3x4, size 12
Product, configured via add_input() method
[obj] Product: 1 transformation(s)
0 [trans] product: 5 input(s), 1 output(s)
0 [in] input_00 -> [out] points: array 2d, shape 3x4, size 12
1 [in] input_01 -> [out] points: array 2d, shape 3x4, size 12
2 [in] input_02 -> [out] points: array 2d, shape 3x4, size 12
3 [in] input_03 -> [out] points: array 2d, shape 3x4, size 12
4 [in] input_04 -> [out] points: array 2d, shape 3x4, size 12
0 [out] product: array 2d, shape 3x4, size 12
Results:
[[0.0000e+00 1.2000e+02 7.2000e+02 2.5200e+03]
[6.7200e+03 1.5120e+04 3.0240e+04 5.5440e+04]
[9.5040e+04 1.5444e+05 2.4024e+05 3.6036e+05]]
[[0.0000e+00 1.2000e+02 7.2000e+02 2.5200e+03]
[6.7200e+03 1.5120e+04 3.0240e+04 5.5440e+04]
[9.5040e+04 1.5444e+05 2.4024e+05 3.6036e+05]]
[[0.0000e+00 1.2000e+02 7.2000e+02 2.5200e+03]
[6.7200e+03 1.5120e+04 3.0240e+04 5.5440e+04]
[9.5040e+04 1.5444e+05 2.4024e+05 3.6036e+05]]
5.4. Simplified syntax for working with inputs and outputs¶
Now let us review the python syntax used to bind transformations together. Let us start with an array with one element:
1import gna.constructors as C
2
3number = 1.2345
4p0 = C.Points([number])
By default points is an object with one transformation and one output, which may be accessed as follows:
1print(p0)
2print(p0.transformations[0])
3print(p0.transformations[0].outputs[0])
The method single()
may be used to return the single output of a single object:
1print(p0.single())
Which is equivalent to:
1print(p0.transformations[0].outputs[0])
Transformation also has a method single()
which returns the single output of a transformation. Both methods raise an
exception if single output can not be returned or the output is not single.
The default way to connect the output to the input is to use input.connect(output)
method:
1s1 = C.Sum()
2s1.add_input('first')
3# Default binding syntax
4s1.sum.inputs[0].connect(p0.points.outputs[0])
Single notation may also be used in a similar way:
1s2 = C.Sum()
2# Siplified access
3s2.add_input('first')
4s2.single_input().connect(s1.single())
Here the method single_input()
returns a single input of a single transformation.
There exist robust operators >>
and <<
that may be used to connect output to input as output>>input
:
1s3 = C.Sum()
2s3.add_input('first')
3# Even simpler way
4s2.sum.sum >> s3.sum.first
or as input<<output
:
1s4 = C.Sum()
2s4.add_input('first')
3# The other direction
4s4.sum.first << s3.sum.sum
Operators <<
and >>
may take care in cases when there is a single input/output in the transformation:
1s5 = C.Sum()
2s5.add_input('first')
3# Even simpler way
4s4.sum >> s5.sum
or when there is a single transformation in the object:
1s5 = C.Sum()
2s5.add_input('first')
3# Even simpler way
4s4 >> s5
The short version may go either on the left hand side, the right hand side or both sides of the expression.
More then one inputs may be passed. In this case the output is connected to each of the inputs, like this:
1s6 = C.Sum()
2s6.add_input('first')
3s6.add_input('second')
4# Bind a single output to two inputs
5s5 >> s6.sum.inputs.values()
or like this:
1s7 = C.Sum()
2s7.add_input('first')
3s7.add_input('second')
4# Bind a single output to two inputs (explicity)
5s6 >> (s7.sum.inputs.first, s7.sum.inputs.second)
In the above example we created a chain that passes as single value transformation to transformation. In the last two examples we have doubled the value twice. Therefore the output should contain the value four times larger:
1result = s7.single().data()[0]
2
3print('Expect:', number*4)
4print('Got: ', result)
Which is confirmed by the output:
Expect: 4.938
Got: 4.938