Tensors
<!– crosslinks: written by .github/scripts/crosslinks.py from docs/crosslinks.toml; do not edit –>
See also
Theory: Bases and variance · Notation and conventions · Tensor algebra · Kelvin–Mandel representation · Rotations and Euler angles
Manual: Bases · Random tensors
Tutorials: Bases, variance and the metric · Tensor products, contractions and their identities · Performance of the structured types · A two-point Green tensor between offset frames
<!– /crosslinks –>
Building tensors, moving their components between bases and variances, and reading them back. The underlying notions are on Bases and variance and Tensor algebra.
Construction
A tensor is an array, a basis and a variance tuple. Supplying fewer arguments fills in the defaults — the canonical basis and fully contravariant variance.
using TensND, SymPyPythonCall, Tensors
V = Tens(Tensor{1, 3}(i -> symbols("v$i", real = true)))
T = Tens(Tensor{2, 3}((i, j) -> symbols("t$i$j", real = true)))
typeof(V), typeof(T)(TensND.TensCanonical{1, 3, Sym{PythonCall.Py}, Tensors.Vec{3, Sym{PythonCall.Py}}}, TensND.TensCanonical{2, 3, Sym{PythonCall.Py}, Tensors.Tensor{2, 3, Sym{PythonCall.Py}, 9}})The type follows the basis: a tensor on a canonical basis is a TensCanonical, on a rotated one a TensRotated, on an orthogonal one a TensOrthogonal, and only the general case is a Tens. Only the last two carry a variance tuple, because on an orthonormal basis the distinction collapses.
ℬ = Basis(Sym[1 0 0; 0 1 0; 0 1 1])
typeof(Tens(get_array(T), ℬ, (:cov, :cov)))Tens{2, 3, Sym{PythonCall.Py}, Tensors.Tensor{2, 3, Sym{PythonCall.Py}, 9}, Basis{3, Sym}}Data may come from a plain Array, or from a Tensors.jl container (Tensor, SymmetricTensor, Vec), in which case the symmetry is preserved.
Components
| Call | Returns |
|---|---|
components(t, ℬ, var) | components on ℬ with variance var |
components(t, var) | components on the tensor's own basis |
components(t, ℬ) | keeps the tensor's variance |
canonical_components(t) | components in the canonical basis |
get_array(t) | the stored array, as is |
components(V, ℬ, (:cont,))components(V, ℬ, (:cov,))get_array is not components
get_array returns the array as stored, in the tensor's own basis and variance. Two tensors that are mathematically equal can have completely different get_arrays. Comparing raw arrays is therefore meaningless unless both tensors are known to share a basis and a variance — bring them to a common one with components or express first. This is a real source of bugs; a worked example is on Submanifolds.
Indexing, t[i, j, …], reads the same stored array. The structured types (TensISO, TensTI, TensOrtho, TensCubic) store their coefficients and answer in the canonical basis, while a plain Tens answers in its own: code tried only on structured or canonical tensors looks right, and is wrong for a tensor written in a rotated basis. Test such code with tensors written in bases drawn at random, each operand in its own.
== and ≈ compare tensors, not arrays: in the basis the two share, with no change at all when they are written in the same basis with the same variances, and otherwise in the common basis same_basis_same_var chooses.
Changing basis
express returns a tensor re-expressed on another basis, where components returns a bare array:
𝐞ᶿ, 𝐞ᵠ, 𝐞ʳ = init_spherical()[2]
ℬˢ = init_spherical()[3]
a = Tens(Vec{3}((i,) -> symbols("a$i", real = true)))
A = Tens(rotation_matrix(symbols("θ", real = true), symbols("ϕ", real = true)) * get_array(a))
simplify(express(A, ℬˢ))canonical is the shorthand for the canonical basis.
Accessors
| Function | Returns |
|---|---|
get_order | the order |
get_dim | the dimension |
get_basis | the basis |
get_var | the variance tuple |
get_data | the stored data (coefficients, for structured types) |
get_array | the full component array |
Products
The full table, with index formulas, is on Tensor algebra. In practice:
using TensND, SymPyPythonCall, Tensors, LinearAlgebra
𝟏 = tens_Id2(Val(3), Val(Sym))
𝕀, 𝕁, 𝕂 = iso_projectors(Val(3), Val(Sym))
𝕀 == 𝟏 ⊠ˢ 𝟏, 𝕁 == (𝟏 ⊗ 𝟏) / 3(true, true)a = Tens(Vec{3}((i,) -> symbols("a$i", real = true)))
b = Tens(Vec{3}((i,) -> symbols("b$i", real = true)))
get_array(a ⊗ˢ b)| Julia | Function | Contracted indices |
|---|---|---|
⊗ | otimes | none |
⊗ˢ | sotimes | none, symmetrized |
⊠ | otimesu | none |
⊠ˢ | otimesul | none, symmetrized |
⋅ | dot | 1 |
⊡ | dcontract | 2 |
⊙ | qcontract | 4 |
Display
pprint prints a tensor expanded on its basis — as a sum of basis products rather than as a component array, which is far more readable for symbolic results:
Spherical = coorsys_spherical()
𝐞ᶿ, 𝐞ᵠ, 𝐞ʳ = unitvec(Spherical)
pprint(𝐞ʳ ⊗ 𝐞ʳ + 2 * 𝐞ᶿ ⊗ 𝐞ᶿ, Spherical)(2)𝐞ᶿ⊗𝐞ᶿ + 𝐞ʳ⊗𝐞ʳApplied to a scalar it falls back to the rich text/plain display, so a symbolic expression comes out in SymPy's two-dimensional form:
laplacian(1 / get_coords(Spherical)[3], Spherical) |> pprint0Structured types print in their compact algebraic form instead — see Structured tensors.
Kelvin–Mandel
mandel maps a symmetric order-2 tensor to a 6-vector and a minor-symmetric order-4 tensor to a from_mandel inverts it. The convention and what it buys are on Kelvin–Mandel representation.
C = ti_from_constants(10.0, 3.0, 2.5, 12.0, 2.0, [0.0, 0.0, 1.0])
mandel(C)6×6 Matrix{Float64}:
10.0 3.0 2.5 0.0 0.0 0.0
3.0 10.0 2.5 0.0 0.0 0.0
2.5 2.5 12.0 0.0 0.0 0.0
0.0 0.0 0.0 4.0 0.0 0.0
0.0 0.0 0.0 0.0 4.0 0.0
0.0 0.0 0.0 0.0 0.0 7.0Symmetry predicates
issymmetric, isminorsymmetric and ismajorsymmetric test the index symmetries; isisotropic, istransverselyisotropic, isorthotropic test the material symmetry class and are documented under Projection.