Tensor#

This module provides the base class for a tensor.

Core

Tensor(x, δx=None, Δx=None, Δδx=None[, ...])

A Hyper-Dual Tensor.

Detailed API Reference

class tensortrax.Tensor(x, δx=None, Δx=None, Δδx=None, ntrax=0, ndual=0)[source]#

A Hyper-Dual Tensor.

Parameters:
  • x (array_like) – The data of the tensor.

  • δx (array_like or None, optional) – (Dual) variation data (δ-operator) of the tensor.

  • Δx (array_like or None, optional) – (Dual) variation data (Δ-operator) of the tensor.

  • Δδx (array_like or None, optional) – (Dual) linearization data (Δδ-operator) of the tensor.

  • ntrax (int, optional) – Total number of trailing axes including dual axes (default is 0).

  • ndual (int, optional) – Number of axes containing dual data (default is 0).

Examples

A hyper-dual tensor consists of batches of tensors, where the elementwise-operating batches are located at the last dimensions (trailing axes). For example, consider two second-order tensors in 3d-space as shown in Eq. (1).

(1)#\[ \begin{align}\begin{aligned}\begin{split}\boldsymbol{T}_1 &= \begin{bmatrix} 0 & 3 & 6 \\ 1 & 4 & 7 \\ 2 & 5 & 8 \end{bmatrix}\end{split}\\\begin{split}\boldsymbol{T}_2 &= \begin{bmatrix} 9 & 12 & 15 \\ 10 & 13 & 16 \\ 11 & 14 & 17 \end{bmatrix}\end{split}\end{aligned}\end{align} \]

This tensor is created with one elementwise-operating trailing axis.

>>> import tensortrax as tr
>>> import numpy as np
>>>
>>> T = tr.Tensor(np.arange(18).reshape(2, 3, 3).T, ntrax=1)
>>> T
<tensortrax tensor object>
  Shape / size of trailing axes: (2,) / 1
  Shape / size of tensor: (3, 3) / 9

Data (first batch):

array([[0, 3, 6],
       [1, 4, 7],
       [2, 5, 8]])

A tensor, which is created without any dual data, is treated as constant and hence, all dual data arrays are initially filled with zeros.

Tip

Broadcasting is used to enhance performance and to reduce memory consumption. If no dual data arrays are provided, the trailing axes of the dual arrays are compressed.

>>> T.x.shape
(3, 3, 2)
>>> T.δx.shape
(3, 3, 1)

If the gradient (or jacobian) w.r.t. this tensor should be recorded, the tensor must be initiated with the argument gradient=True. This will add additional dual axes to the data arrays of the tensor. These axes are used for the gradient dimension.

>>> T.init(gradient=True)
>>> T
<tensortrax tensor object>
  Shape / size of trailing axes: (1, 1, 2) / 3
  Shape / size of tensor: (3, 3) / 9

Data (first batch):

array([[0, 3, 6],
       [1, 4, 7],
       [2, 5, 8]])

Again, broadcasting is used, see the shapes of the tensor data arrays.

>>> T.x.shape
(3, 3, 1, 1, 2)
>>> T.δx.shape
(3, 3, 3, 3, 1)

If we look at the dual array, this is the (non-symmetric) fourth-order identity tensor, see Eq. (2). This identity tensor has values of one located at the component to be differentiated and is filled with zeros otherwise.

(2)#\[\frac{\partial T_{ij}}{\partial T_{kl}} = \delta_{ik}\ \delta_{jl}\]
>>> T.δx[0, 1, ..., 0]
array([[0., 1., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])
>>> T.δx[1, 2, ..., 0]
array([[0., 0., 0.],
       [0., 0., 1.],
       [0., 0., 0.]])
>>> T.δx.reshape(9, 9)
array([[1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 1., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 1.]])

Now let’s perform some math, e.g. the dot product as shown in Eq. (3). Along with the evaluation of the dot-product, the partial derivative of \(\boldsymbol{V}\) w.r.t. \(\boldsymbol{T}\) is recorded in the dual data array.

(3)#\[ \begin{align}\begin{aligned}\boldsymbol{V} &= \boldsymbol{T}^T\ \boldsymbol{T}\\\delta \boldsymbol{V} &= \delta \boldsymbol{T}^T\ \boldsymbol{T} + \boldsymbol{T}^T\ \delta \boldsymbol{T}\end{aligned}\end{align} \]
>>> V = T.T @ T
>>> V
<tensortrax tensor object>
  Shape / size of trailing axes: (1, 1, 2) / 3
  Shape / size of tensor: (3, 3) / 9

Data (first batch):

array([[  5,  14,  23],
       [ 14,  50,  86],
       [ 23,  86, 149]])
>>> V.δx[1, 2, ..., 0]
array([[0., 6., 3.],
       [0., 7., 4.],
       [0., 8., 5.]])

See also

tensortrax.function

Evaluate a function.

tensortrax.gradient

Evaluate the gradient of a scalar-valued function.

tensortrax.jacobian

Evaluate the jacobian of a function.

tensortrax.hessian

Evaluate the hessian of a scalar-valued function.

property T#
astype(dtype)[source]#
copy()[source]#

Copy the Tensor.

dual2real(like)[source]#
dual_to_real(like)[source]#
init(gradient=False, hessian=False, sym=False, δx=None, Δx=None)[source]#

Re-Initialize tensor with dual values to keep track of the hessian and/or the gradient.

ravel(order='C')[source]#

Return a contiguous flattened array.

real_to_dual(x, mul=None)[source]#
reset()[source]#

Reset the Tensor (set all dual values to zero).

reshape(*shape, order='C')[source]#

Gives a new shape to an array without changing its data.

squeeze(axis=None)[source]#

Remove axes of length one.