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:

  1. Construct Sum passing list of outputs as an argument.

  2. Construct empty Sum and add each output manually via add() method.

  3. Construct empty Sum, create a set of named inputs via add_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