City SF = City.ofName("San Francisco");
City LA = City.ofName("Los Angeles");
Time T1 = Time.of("20170101 15:00");
Time T2 = Time.of("20170102 15:00");
Tensor<Double> degrees;
/* creation omitted */
The name "Tensorics" is derived from "Tensor" which in mathematics  loosely spoken  represents a multidimensional data structure whose entries are adressed by indizes. In tensorics, we use the term "Tensor" in an even sloppier manner. The main particularity of a tensorics tensor is that a dimension is identified by a java type (class). Instances of the respective type we denote as coordinates. A value of the tensor is then adressed within the Ndimensional coordinate space by a set of objects (instances of coordinate classes).
A tensorics tensor has one type parameter, the type of the values it contains, usually denoted as <V>
. Therefore, the tensor data structure can be used as container for any Java type. However, some operations on the tensors will be only possible for certain value types (e.g. mathematical operations).
Since tensorics concepts and syntax are best explained in a practical walkthrough, we will use the following example throughout the subsequent sections:
Consider weather analysis: A data set consists of weather data from different cities and times. The class City and Time are defined and some constants are instantiated. Temperature values are stored in a tensor of doubles, for example:
City SF = City.ofName("San Francisco");
City LA = City.ofName("Los Angeles");
Time T1 = Time.of("20170101 15:00");
Time T2 = Time.of("20170102 15:00");
Tensor<Double> degrees;
/* creation omitted */
Assuming the above constants, we can then simply get temperature values from the tensor:
Double t = degrees.get(T1, SF);
As visible here, this looks very similar to getting values from a map, with the following important differences:
The get method of a tensor accepts N arguments, one for each dimension.
The get method of a tensor never returns null
. It will throw an appropriate exception in case there is no value available in the tensor for the given set of coordinates.
In general, it shall be noted that all methods within the tensorics library are designed to fail fast. This is particularly important because \tensorics{}, due to its flexible API, cannot rely on compiletime checks in many cases and thus some errors only appear at runtime.
The set of N coordinates is called a position in tensorics. Thus, the code from the above listing is equivalent to
Position position = Position.of(T1, SF);
Double t = degrees.get(position);
The interfaces of tensorics objects are kept very slim and usually only provide the absolutely necessary methods. All the other operations on these objects is based on static methods operating on them. The main entry point for these methods (containing all the methods which are not specific to certain value types) is the class \code{Tensorics}. This class contains also, for example, a delegation method to the \code{Position.of()} method:
Position position = Tensorics.at(T1, SF);
/* with static import: */
Position position = at(T1, SF);
Using a static import for this, allows concise code which will be particularly important when creating tensors.
In all the following code examples, we assume that, whenever there is a plain method call, then it is a static method from the Tensorics class (or in other words that Tensorics. is imported statically).

All currently available implementations of tensors are immutable. The usual way to create them is through builders. For example, to create our temperature tensor and put 4 values into it, we would have to do something like:
Tensor<Double> degrees =
builder(City.class, Time.class)
.put(at(SF, T1), 12.5)
.put(at(SF, T2), 14.2)
.put(at(LA, T1), 17.5)
.put(at(LA, T2), 19.2)
.build();
Again, the syntax is very similar to building an immutable map. And indeed this is another way how a \tensorics{} tensor can be seen: As a map from position to a value  and it can be transformed into one:
Map<Position, Double> degreesMap = mapFrom(degrees);
A tensor can have zero dimensions. This particular tensor we denote as scalar in \tensorics{}. It has exactly one value at the position Position.empty()
. A scalar can simply be created using the static factory method
Scalar<Double> scalar = scalarOf(2.5);
Up to now, we were simply using a tensor as a kindof map with combined keys. However, the real power is unleashed only when it comes to transformations. As a precondition of this, it is important to introduct the concept of the tensors Shape: Just like a map has its set of keys, a tensorics tensor has a shape. It basically describes the structure of the tensor, without its values. Basically it contains the following information:
 The dimensions of the tensor (e.g. Time.class
and City.class
in the above example) and
 The available positions in the tensor.
The shape can be retrieved from the tensor and used for our example like the following:
Shape shape = degrees.shape();
Set<Class<?>> dims = shape.dimensionSet();
/* Contains Time.class and City.class */
int dim = shape.dimensionality();
/* Will be 2 */
Set<Position> poss = shape.positionSet();
/* contains the 4 positions */
int size = shape.size();
/* Will be 4 */