Tensors

The central object of tensorics is a Tensor. The simplest way to imagine a tensor might be to see it as a kind of map, where the map-key is actually a 'set of keys'. This 'set of keys' can be seen as a position in the N-dimensional space - and this is what we will call it: Position ;-). Each position has exactly N elements (coordinates), corresponding to the dimensionality (N) of the respective tensor. The dimensions of a tensor are uniquely defined by the classes of the coordinates.

This might have sound complicated - however, an example should make it obvious. Give it a chance and keep on reading ;-)

The weather example

Lets take a (very simplistic ;-) example: Assume that we are analyzing weather data. We have recorded temperature values for several cities and several days, for example:

Table 1. Example Temperatures (random data!)
City Day Temperature [°C]

New York

April 1, 2014

18.5

New York

June 1, 2014

25.0

Geneva

April 1, 2014

19.8

Geneva

June 1, 2014

24.7

These measurements can be represented as a tensor with two dimensions (city and day) and double values. Such a tensor can be created as follows:

Building a Tensor
TensorBuilder<Double> builder = Tensorics.builder(City.class, Day.class); (1)

builder.put(at(City.NEW_YORK, Day.APRIL_1_2014), 18.5); (2)
builder.put(at(City.GENEVA, Day.APRIL_1_2014), 19.8);
builder.put(at(City.GENEVA, Day.JUNE_1_2014), 24.7);

Tensor<Double> temps = builder.build(); (3)
1 Create the builder. The dimensions have to be given here. Dimensions are the classes of the coordinates.
2 Put values to the builder. For each class, given as dimension, exactly one instance has to be given.
3 Create the tensor.

Hereby we assumed that the cities and days are defined as constants beforehand (or enum values). Ok, we are aware that it is very simplistic assumption and not practical for a real application ;-) The point is, it does not matter: Any instance of a java class can serve as a coordinate. However, There are some much more important points to be learned from this example:

Tensors are always immutable! Therefore always a builder has to be used to create a tensor (or some convenience factory methods).
There is one global entry point: The Tensorics class. It provides access to static methods of the most important functionality of tensorics. It is convenient to use these methods by putting as a static import to your java files.
The 'put' - like methods in the builder are kind-of-reversed compared to put methods of a map: The parameters for coordinates (which basically build the key/position) follow the parameter for the value. This might be counter-intuitive at a first glance. However, there is a strong reason for this: The number of coordinates is variable for tensors, while there is only one value. Therefore, to take advantage of the java varargs feature, it is necessary to place the coordinate-arguments after the value argument.

Using Tensors

The simplest way to think of a tensor (in the sense of tensorics) is to imagine a map from positions to values. Therefore, values can be retrieved from the tensor in a similar way than from a map:

Getting Values
double temps = temperatures.get(City.NEW_YORK, Day.APRIL_1_2014);
All methods in tensorics are designed to fail fast. As a consequence, e.g. the get method will throw a NoSuchElementException, if the requested element is not contained in the tensor. Further, an IllegalArgumentException might be thrown, if the number of the given coordinates do not match the dimensions of the tensor.

Again similar to maps, there exists also an easy way to loop through the position/value pairs:

Looping through tensor entries
for (Entry<Position, Double> entry : Tensorics.mapFrom(temperatures).entrySet()) {
    Position position = entry.getKey();
    double value = entry.getValue();
    System.out.println(position + "\t->\t" + value);
}

This would print to the console something like this:

[NEW_YORK, APRIL_1_2014]	->	18.5
[GENEVA, APRIL_1_2014]  	->	19.8
[NEW_YORK, JUNE_1_2014] 	->	25.0
[GENEVA, JUNE_1_2014]   	->	24.7

Tensor Shape

Very often it is useful to see what for which positions in the N-dimensional space values are available within a tensor. This is comparable to the key-set of a map. However, there are several important differences:

  • Tensors can have different dimensions (number and types of coordinates), while a map has exactly one type of the key. This information is very important to compare different tensors, for example to determine if the two tensors are compatible for certain operations or not.

  • Very often there will be similar tensors, which contain the same positions but different values: For example, if a tensor (in mathematics) is multiplied by a scalar value, the resulting tensor will be of the same 'Shape' as the original tensor.

A dedicated object, the Shape holds all this information and can be retrieved from a tensor by the shape() method. Like most objects in the tensorics library, a shape is also an immutable object.

Structural Operations

In the above example, the get() method of the tensor was used to get values at a certain position. However, you might notice that the API of the tensor interface appears to be quite sparse: It contains only the really necessary methods to access the data of the tensor. The philosophy of the tensorics library in this context is, that all more complicated manipulations are done by the use of static utility methods. This allows to easily extend the library by new functionality without breaking the core interfaces. Many of this utility methods are implemented such, that they are starting points for fluent clauses, which allows a (hopefully) very expressive and intuitive usage. The second reason for this decision is, that in this way it is possible to provide an API with the same syntax for other objects, like numbers or tensor expressions (To be explained later ;-).

So, instead of the above example, which uses the get() method directly, it is equivalent to write:

double temperature = Tensorics.from(temperatures).get(City.NEW_YORK, Day.APRIL_1_2014);

Together, with static imports for the from() method and the enum constants, this reads even nicer as:

double temperature = from(temperatures).get(NEW_YORK, APRIL_1_2014);

So far, this is of course not very impressive. However, the from(..) clause provides various other methods which provide access to the data of the tensor. The most important ones of them are: .Structural Operations

clause description

from(tensor).get(Object…​ coordinates);

Retrieves the value at the given coordinates.

from(tensor).extract(Object…​ coordinates);

Extracts the sub tensor at the given coordinates.

Last built: 2019-07-30 00:16:37 CEST