Tensor — arbitrary-rank tensor

Overview

Tensor<T> is sangi's arbitrary-rank, arbitrary-shape dynamic tensor class. It unifies the treatment of matrices, vectors, and higher-dimensional arrays, and is used for multi-dimensional array representation in machine learning and physical simulation. Header-only; no library linking required.

  • Dynamic rank and shape — any number of dimensions specified at runtime, e.g. Tensor<T>({3, 4, 5})
  • Stride-based viewstranspose and reshape return views without copying data
  • shared_ptr storage — multiple tensors can safely share the underlying buffer; writes through views trigger an independent copy when necessary (copy-on-write style)
  • Matrix / Vector interop — rank-2 ↔ Matrix<T>, rank-1 ↔ Vector<T>, rank-0 ↔ scalar
  • C++23 concepts — element type constrained by TensorScalar

For the mathematical background, see Tensors (Linear Algebra, intermediate) / Matrix and tensor calculus.

TensorScalar concept

The element type T of Tensor<T> must satisfy:

template<typename T>
concept TensorScalar = requires(T a, T b) {
    { a + b } -> std::convertible_to<T>;
    { a - b } -> std::convertible_to<T>;
    { a * b } -> std::convertible_to<T>;
    { -a }    -> std::convertible_to<T>;
    { T(0) };
};

That is, types that support addition, subtraction, multiplication, unary minus, and zero initialization. Built-in float, double, std::complex<T> satisfy this, as do sangi's arbitrary-precision types Int, Float, Rational, Complex.

Tensor<T> class

template<TensorScalar T>
class Tensor;

Constructors

SignatureDescription
Tensor()Default construction. rank-0 scalar with value 0
Tensor(std::vector<size_t> shape)Zero-initialized tensor of the given shape
Tensor(std::initializer_list<size_t> shape)Same (initializer-list overload)
Tensor(std::vector<size_t> shape, const T& value)All entries initialized to value
Tensor(std::vector<size_t> shape, std::vector<T> data)Constructed from flat data (row-major). Throws DimensionError on size mismatch
Tensor(const T& scalar) (explicit)rank-0 scalar tensor
Tensor(const Matrix<T>& matrix) (explicit)Convert Matrix to rank-2 tensor
Tensor(const Vector<T>& vec) (explicit)Convert Vector to rank-1 tensor
Tensor(const Tensor&) / Tensor(Tensor&&) noexceptCopy / move (the internal shared_ptr is shared)
Tensor<double> t0;                       // rank-0 scalar (value 0)
Tensor<double> t1({3, 4, 5});            // rank-3, zero-initialized (60 entries)
Tensor<double> t2({2, 3}, 1.0);          // rank-2, all entries 1.0
Tensor<double> t3({2, 3}, {1,2,3,4,5,6}); // from flat data (row-major)
Tensor<double> s(3.14);                  // rank-0 scalar

Element access

MemberDescription
operator()(Indices... indices)variadic-template overload. Throws DimensionError if the number of indices differs from rank()
at(std::span<const size_t> indices)Bounds-checked access
at(std::initializer_list<size_t> indices)Same (initializer-list overload)
flat(size_t index)Logical flat-index access (in stride order)
data()Raw pointer into the storage (with offset_ applied)
Tensor<double> t({2, 3}, {1,2,3,4,5,6});
double x = t(1, 2);                       // 6 (row 1, col 2)
double y = t.at({0, 1});                  // 2 (bounds-checked)
double z = t.flat(3);                     // 4 (flat index)
double* p = t.data();                     // raw pointer

Properties

MemberReturnsDescription
rank()size_tRank (number of dimensions). A rank-0 scalar returns 0
shape()const std::vector<size_t>&Per-axis sizes
shape(size_t axis)size_tSize of one axis
strides()const std::vector<size_t>&Strides (in elements)
size()size_tTotal number of entries
empty()boolTrue if any axis has size 0
isContiguous()boolWhether memory is row-major contiguous (false after transpose)
isScalar()boolTrue if this is a rank-0 scalar
isView()boolTrue if the storage is shared with another tensor (use_count > 1)
useCount()longReference count of the internal shared_ptr

Mutation

MemberDescription
fill(const T& value)Set every entry to value. Creates an independent copy if this is a view
zero()Set every entry to zero

Shape operations

MemberDescription
reshape(std::vector<size_t> newShape)Change shape if the total element count matches. Returns a view (shared storage) when contiguous; otherwise a copy. Throws DimensionError on mismatch
transpose(std::vector<size_t> perm)Returns a view with axes permuted by perm (no data copy). perm must be a permutation of 0..rank-1
transpose()Reverse all axes (equivalent to ordinary matrix transpose for rank 2)
contiguous()Returns a contiguous independent copy. Returns *this if already contiguous and uniquely owned
Tensor<double> t({2, 3, 4});
auto r  = t.reshape({6, 4});             // view (shared storage)
auto tT = t.transpose({2, 1, 0});        // axis (0,1,2) → (2,1,0), view
auto m  = t.transpose();                 // reverse all axes (rank-3 → {2,1,0})
auto c  = tT.contiguous();               // physically row-major independent copy

Arithmetic (compound assignment)

OpDescription
t += rhsElement-wise addition; shapes must match (DimensionError otherwise)
t -= rhsElement-wise subtraction
t *= scalarScalar multiplication
t /= scalarScalar division (throws invalid_argument on zero)

Matrix / Vector / scalar conversion

MemberDescription
toMatrix()Convert a rank-2 tensor to Matrix<T>. Throws DimensionError if rank ≠ 2
toVector()Convert a rank-1 tensor to Vector<T>. Throws if rank ≠ 1
toScalar()Convert a rank-0 tensor to a scalar T. Throws if rank ≠ 0

Output

MemberDescription
to_string()String representation including shape and values. For large tensors, shows the first 5 and last 3 entries

A non-member operator<< is provided as well (it calls to_string()).

Free functions

Arithmetic operators

SignatureDescription
Tensor<T> operator+(const Tensor<T>& lhs, const Tensor<T>& rhs)Tensor addition
Tensor<T> operator-(const Tensor<T>& lhs, const Tensor<T>& rhs)Tensor subtraction
Tensor<T> operator*(const Tensor<T>&, const T&) / left formScalar multiplication (both overloads provided)
Tensor<T> operator/(const Tensor<T>&, const T&)Scalar division
Tensor<T> operator-(const Tensor<T>&)Unary minus (entry-wise sign flip)

Tensor operations

SignatureDescription
Tensor<T> hadamard(const Tensor<T>& a, const Tensor<T>& b)Element-wise (Hadamard) product. Shapes must match
Tensor<T> contract(const Tensor<T>& a, const Tensor<T>& b, const std::vector<std::pair<size_t,size_t>>& axes)Contract over the given pairs of axes. axes[i] = {axis_of_a, axis_of_b}
Tensor<T> outerProduct(const Tensor<T>& a, const Tensor<T>& b)Outer product (tensor product). Result rank = rank(a) + rank(b); shape is the concatenation
// Hadamard product (element-wise)
Tensor<double> a({2,3}, {1,2,3,4,5,6});
Tensor<double> b({2,3}, {6,5,4,3,2,1});
auto h = hadamard(a, b);   // shape = {2,3}, data = {6, 10, 12, 12, 10, 6}

// Matrix product (rank-2 x rank-2, contract axis 1 of a with axis 0 of b)
Tensor<double> M({3,4}, 1.0);
Tensor<double> N({4,5}, 2.0);
auto C = contract(M, N, {{1, 0}});   // shape = {3, 5}, every entry = 8 (= 4×1×2)

// Outer product (rank-1 x rank-1 = rank-2)
Tensor<double> u({3}, {1,2,3});
Tensor<double> v({2}, {10,20});
auto K = outerProduct(u, v);   // shape = {3,2}, data = {10,20,20,40,30,60}
                               // = {{10,20},{20,40},{30,60}}
// Run output (build & run verified):
//   h shape=[2,3] data=[6,10,12,12,10,6]
//   C shape=[3,5] data=[8,8,8,8,8, 8,8,8,8,8, 8,8,8,8,8]
//   K shape=[3,2] data=[10,20,20,40,30,60]

Factory functions

Provided in namespace sangi::tensor.

SignatureDescription
tensor::zeros<T>(std::vector<size_t> shape)Tensor of zeros
tensor::ones<T>(std::vector<size_t> shape)Tensor of ones

Type traits

template<typename T> struct is_tensor;
template<typename T> inline constexpr bool is_tensor_v = is_tensor<T>::value;

is_tensor_v<X> is true when X is an instance of Tensor<T>. Useful for SFINAE / concept dispatch.

Example

#include <math/core/Tensor.hpp>
using namespace sangi;

int main() {
    // Build a rank-3 tensor and take a reshape view
    Tensor<double> t({2, 3, 4}, 1.0);
    auto t_flat = t.reshape({24});           // view
    t_flat(0) = 42.0;                        // t(0,0,0) is now 42.0 (shared storage)

    // Axis permutation (transpose) is a view; contiguous() copies
    auto t_perm = t.transpose({2, 0, 1});    // shape {4,2,3}, no copy
    auto t_copy = t_perm.contiguous();       // physically row-major independent copy

    // Hadamard product
    Tensor<double> a({2,2}, {1,2,3,4});
    Tensor<double> b({2,2}, {5,6,7,8});
    auto c = hadamard(a, b);                 // {5, 12, 21, 32}

    // Contraction (used as matrix product)
    Tensor<double> X({3, 4}, 1.0);
    Tensor<double> Y({4, 5}, 2.0);
    auto Z = contract(X, Y, {{1, 0}});       // shape {3,5}, each entry 8.0

    // Matrix / Vector interop
    Matrix<double> M(3, 3, 1.0);
    Tensor<double> tM(M);                    // to rank-2 tensor
    Matrix<double> M2 = tM.toMatrix();       // back to Matrix
    // Run output (build & run verified):
    //   t_perm  rank=3, shape={4,2,3}, isContiguous=false
    //   c       shape=[2,2], data=[5,12,21,32]
    //   Z       shape=[3,5], every entry 8.0
    //   tM.rank=2, size=9; M2(0,0)=1, M2(2,2)=1
}

See also: Matrix / Vector / FFT (Tensor3D) / Matrix arithmetic — Hadamard product.