Skip to content

Tensors

The tensor hierarchy, component conversion and the Kelvin-Mandel map. Theory: Tensor algebra; usage: Tensors.

TensND.AbstractTens Type
julia
AbstractTens{order,dim,T}

Supertype of every tensor in TensND: a tensor of a given order and dim, with element type T.

A tensor is not an array. It is an array of components together with the basis those components refer to and the variance of each index, which is what lets tensors expressed in different frames be combined, and what makes comparing raw component arrays meaningless. See Bases and variance.

The concrete subtypes fall in two families:

FamilyTypesStores
generalTens, TensRotated, TensCanonical, TensOrthogonala full component array
structuredTensISO, TensTI, TensOrthoa few scalars plus an orientation

The type follows the basis: only the non-orthonormal cases carry a variance tuple. Accessors common to all of them are get_order, get_dim, get_basis, get_var, get_data and get_array.

TensND.Tens Type
julia
Tens{order,dim,T,A<:AbstractArray}

Tensor type of any order defined by

  • a multidata of components (of any type inheriting from AbstractArray, e.g. Tensor or SymmetricTensor)

  • a basis of AbstractBasis type

  • a tuple of variances (covariant :cov or contravariant :cont) of length equal to the order of the tensor

The variance tuple only matters when the basis is not orthonormal; on an orthonormal basis the covariant and contravariant components coincide, which is why TensCanonical, TensRotated and TensOrthonormal do not carry one. See Bases and variance.

Examples

Storing the covariant metric of a non-orthogonal basis as a twice-covariant tensor, and raising one index — which necessarily returns the identity, since gⁱᵏ gₖⱼ = δⁱⱼ:

julia
julia>= Basis(Sym[1 0 0; 0 1 0; 0 1 1]) ;

julia> T = Tens(metric(ℬ, :cov), ℬ, (:cov, :cov))
3×3 Tens{2, 3, Sym, SymmetricTensor{2, 3, Sym, 6}}:
 1  0  0
 0  2  1
 0  1  1

julia> components(T, (:cont, :cov))
3×3 Matrix{Sym}:
 1  0  0
 0  1  0
 0  0  1

See also components, change_tens, get_var.

TensND.components Function
julia
components(t::AbstractTens{order,dim,T},ℬ::AbstractBasis{dim},var::NTuple{order,Symbol})
components(t::AbstractTens{order,dim,T},ℬ::AbstractBasis{dim})
components(t::AbstractTens{order,dim,T},var::NTuple{order,Symbol})

Extract the components of a tensor for new variances and/or in a new basis

Examples

julia
julia>= Basis(Sym[0 1 1; 1 0 1; 1 1 0]) ;

julia> TV = Tens(Tensor{1,3}(i->symbols("v$i",real=true)))
TensND.TensCanonical{1, 3, Sym, Vec{3, Sym}}
# data: 3-element Vec{3, Sym}:
 v₁
 v₂
 v₃
# basis: 3×3 TensND.LazyIdentity{3, Sym}:
 1  0  0
 0  1  0
 0  0  1
# var: (:cont,)

julia> factor.(components(TV, ℬ, (:cont,)))
3-element Vector{Sym}:
 -(v1 - v2 - v3)/2
  (v1 - v2 + v3)/2
  (v1 + v2 - v3)/2

julia> components(TV, ℬ, (:cov,))
3-element Vector{Sym}:
 v₂ + v₃
 v₁ + v₃
 v₁ + v₂

julia> simplify.(components(TV, normalize(ℬ), (:cov,)))
3-element Vector{Sym}:
 sqrt(2)*(v2 + v3)/2
 sqrt(2)*(v1 + v3)/2
 sqrt(2)*(v1 + v2)/2

julia> TT = Tens(Tensor{2,3}((i,j)->symbols("t$i$j",real=true)))
TensND.TensCanonical{2, 3, Sym, Tensor{2, 3, Sym, 9}}
# data: 3×3 Tensor{2, 3, Sym, 9}:
 t₁₁  t₁₂  t₁₃
 t₂₁  t₂₂  t₂₃
 t₃₁  t₃₂  t₃₃
# basis: 3×3 TensND.LazyIdentity{3, Sym}:
 1  0  0
 0  1  0
 0  0  1
# var: (:cont, :cont)

julia> components(TT, ℬ, (:cov,:cov))
3×3 Matrix{Sym}:
 t₂₂ + t₂₃ + t₃₂ + t₃₃  t₂₁ + t₂₃ + t₃₁ + t₃₃  t₂₁ + t₂₂ + t₃₁ + t₃₂
 t₁₂ + t₁₃ + t₃₂ + t₃₃  t₁₁ + t₁₃ + t₃₁ + t₃₃  t₁₁ + t₁₂ + t₃₁ + t₃₂
 t₁₂ + t₁₃ + t₂₂ + t₂₃  t₁₁ + t₁₃ + t₂₁ + t₂₃  t₁₁ + t₁₂ + t₂₁ + t₂₂

julia> factor.(components(TT, ℬ, (:cont,:cov)))
3×3 Matrix{Sym}:
 -(t12 + t13 - t22 - t23 - t32 - t33)/2  -(t11 + t12 - t21 - t22 - t31 - t32)/2
  (t12 + t13 - t22 - t23 + t32 + t33)/2      (t11 + t12 - t21 - t22 + t31 + t32)/2
  (t12 + t13 + t22 + t23 - t32 - t33)/2      (t11 + t12 + t21 + t22 - t31 - t32)/2
TensND.components_canon Function
julia
components_canon(t::AbstractTens)

Extract the components of a tensor in the canonical basis

TensND.change_tens Function
julia
change_tens(t::AbstractTens{order,dim,T},ℬ::AbstractBasis{dim},var::NTuple{order,Symbol})
change_tens(t::AbstractTens{order,dim,T},ℬ::AbstractBasis{dim})
change_tens(t::AbstractTens{order,dim,T},var::NTuple{order,Symbol})

Rewrite the same tensor with components corresponding to new variances and/or to a new basis

julia
julia>= Basis(Sym[0 1 1; 1 0 1; 1 1 0]) ;

julia> TV = Tens(Tensor{1,3}(i->symbols("v$i",real=true)))
TensND.TensCanonical{1, 3, Sym, Vec{3, Sym}}
# data: 3-element Vec{3, Sym}:
 v₁
 v₂
 v₃
# basis: 3×3 TensND.LazyIdentity{3, Sym}:
 1  0  0
 0  1  0
 0  0  1
# var: (:cont,)

julia> factor.(components(TV, ℬ, (:cont,)))
3-element Vector{Sym}:
 -(v1 - v2 - v3)/2
  (v1 - v2 + v3)/2
  (v1 + v2 - v3)/2

julia> ℬ₀ = Basis(Sym[0 1 1; 1 0 1; 1 1 1]) ;

julia> TV0 = change_tens(TV, ℬ₀)
Tens{1, 3, Sym, Vec{3, Sym}}
# data: 3-element Vec{3, Sym}:
     -v₁ + v₃
     -v₂ + v₃
 v₁ + v₂ - v₃
# basis: 3×3 Tensor{2, 3, Sym, 9}:
 0  1  1
 1  0  1
 1  1  1
# var: (:cont,)
TensND.change_tens_canon Function
julia
change_tens_canon(t::AbstractTens{order,dim,T},var::NTuple{order,Symbol})

Rewrite the same tensor with components corresponding to the canonical basis

julia
julia>= Basis(Sym[0 1 1; 1 0 1; 1 1 0]) ;

julia> TV = Tens(Tensor{1,3}(i->symbols("v$i",real=true)), ℬ)
Tens{1, 3, Sym, Vec{3, Sym}}
# data: 3-element Vec{3, Sym}:
 v₁
 v₂
 v₃
# basis: 3×3 Tensor{2, 3, Sym, 9}:
 0  1  1
 1  0  1
 1  1  1
# var: (:cont,)

julia> TV0 = change_tens_canon(TV)
TensND.TensCanonical{1, 3, Sym, Vec{3, Sym}}
# data: 3-element Vec{3, Sym}:
      v₂ + v₃
      v₁ + v₃
 v₁ + v₂ + v₃
# basis: 3×3 TensND.LazyIdentity{3, Sym}:
 1  0  0
 0  1  0
 0  0  1
# var: (:cont,)
TensND.get_order Function
julia
get_order(t::AbstractTens)  Int

Order (number of indices) of t.

TensND.get_basis Function
julia
get_basis(t::AbstractTens)  AbstractBasis

Basis the components of t refer to. Structured types report the canonical basis, their orientation being carried separately by axis or frame.

TensND.get_var Function
julia
get_var(t::AbstractTens)  NTuple{order,Symbol}
get_var(t::AbstractTens, i::Integer)  Symbol

Variance of each index of t, :cov (covariant) or :cont (contravariant).

On an orthonormal basis the two coincide, so tensors stored on one report a constant tuple and the distinction may be ignored. See Bases and variance.

TensND.get_data Function
julia
get_data(t::AbstractTens)

The stored data: the coefficient tuple for a structured tensor (TensISO, TensTI, TensOrtho), the component array otherwise. Use get_array when the full component array is wanted in every case.

TensND.get_array Function
julia
get_array(t::TensTI{4, T})  Array{T,4}

Compute the 3×3×3×3 component array from the Walpole coefficients and axis.

julia
get_array(t::TensTI{2,T,2})  Array{T,2}

Compute the 3×3 component array: a*(δᵢⱼ − nᵢnⱼ) + b*nᵢnⱼ.

Examples

julia
julia> A = TensTI{2}(5.0, 8.0, [0., 0., 1.]);

julia> get_array(A)
3×3 Matrix{Float64}:
 5.0  0.0  0.0
 0.0  5.0  0.0
 0.0  0.0  8.0
julia
get_array(t::TensCubic{T}) -> Array{T,4}

The 3×3×3×3 component array in the canonical frame.

Evaluated through _ortho_entry with C₁₁ = C₂₂ = C₃₃, C₁₂ = C₁₃ = C₂₃ and C₄₄ = C₅₅ = C₆₆, which is what a cubic tensor is as an orthotropic one — no second copy of that algebra.

TensND.pprint Function
julia
pprint(t::AbstractTens; vec = '𝐞', coords = 1:dim)
pprint(t::AbstractTens, CS::AbstractCoorSystem; vec = '𝐞')

Print t expanded on its basis — as a sum of basis products such as (σʳʳ)𝐞ʳ⊗𝐞ʳ + (σᶿᶿ)𝐞ᶿ⊗𝐞ᶿ — instead of as an array of components.

This is a display helper: it writes to stdout and returns nothing. For symbolic results it is far more readable than the component array, and it is the natural way to report a field computed in a curvilinear frame. Zero components are omitted, and a vanishing tensor prints as 0.

Passing a coordinate system names the basis vectors after its coordinates; @set_coorsys makes that the default, so pprint(t) alone then prints 𝐞ʳ, 𝐞ᶿ, … rather than 𝐞₁, 𝐞₂, ….

Formerly intrinsic, then print_tensor

intrinsic was misleading: what is printed is explicitly basis dependent — it changes with the chart — whereas "intrinsic" denotes the basis-free form. print_tensor was misleading in turn, because the fallback below accepts a scalar, and pprint is the name MPCM already uses for a generic pretty-printer (ChemistryLab.pprint). Both former names still work and forward here, with a deprecation warning.

Examples

julia
julia> Spherical = coorsys_spherical() ; 𝐞ᶿ, 𝐞ᵠ, 𝐞ʳ = unitvec(Spherical) ;

julia> @set_coorsys Spherical

julia> pprint(𝐞ʳ  𝐞ʳ + 2 * 𝐞ᶿ  𝐞ᶿ)   # spherical coordinates are ordered (θ, ϕ, r)
(2)𝐞ᶿ𝐞ᶿ + 𝐞ʳ𝐞ʳ

Applied to anything that is not a tensor — a scalar field, typically, so that LAPLACE(f(r)) |> pprint works in a pipeline — it falls back to the rich text/plain display, which for a symbolic expression is SymPy's two-dimensional form:

julia
julia> LAPLACE(SymFunction("f", real = true)(r)) |> pprint
              d
 2          2──(f(r))
d             dr
───(f(r)) + ──────────
  2             r
dr

Tensor coefficients are deliberately not printed that way: a multi-line coefficient interpolated into (…)𝐞ʳ⊗𝐞ʳ + (…)𝐞ᶿ⊗𝐞ᶿ would destroy the layout that makes the expanded form readable in the first place.

See also get_array, components.

TensND.KM Function
julia
KM(t::AbstractTens{order,dim}; kwargs...)
KM(t::AbstractTens{order,dim}, var::NTuple{order,Symbol}, b::AbstractBasis{dim}; kwargs...)

Write the components of a second or fourth order tensor in Kelvin-Mandel notation

Examples

julia
julia> σ = Tens(SymmetricTensor{2,3}((i, j) -> symbols($i$j", real = true))) ;

julia> KM(σ)
6-element Vector{Sym}:
         σ11
         σ22
         σ33
2σ32
2σ31
2σ21

julia> C = Tens(SymmetricTensor{4,3}((i, j, k, l) -> symbols("C$i$j$k$l", real = true))) ;

julia> KM(C)
6×6 Matrix{Sym}:
         C₁₁₁₁     C₁₁₂₂     C₁₁₃₃  2C₁₁₃₂  2C₁₁₃₁  2C₁₁₂₁
         C₂₂₁₁     C₂₂₂₂     C₂₂₃₃  2C₂₂₃₂  2C₂₂₃₁  2C₂₂₂₁
         C₃₃₁₁     C₃₃₂₂     C₃₃₃₃  2C₃₃₃₂  2C₃₃₃₁  2C₃₃₂₁
2C₃₂₁₁  2C₃₂₂₂  2C₃₂₃₃   2C₃₂₃₂   2C₃₂₃₁   2C₃₂₂₁
2C₃₁₁₁  2C₃₁₂₂  2C₃₁₃₃   2C₃₁₃₂   2C₃₁₃₁   2C₃₁₂₁
2C₂₁₁₁  2C₂₁₂₂  2C₂₁₃₃   2C₂₁₃₂   2C₂₁₃₁   2C₂₁₂₁
julia
KM(A::TensISO{2,dim}) -> Vector
KM(A::TensISO{4,dim}) -> Matrix

Kelvin-Mandel vector or matrix representation of an isotropic tensor, in closed form.

With m = dim(dim+1)/2 the Mandel size, λ𝟏 is the vector whose first dim entries are λ, and α𝕁 + β𝕂 is

E being the matrix with ones on the leading dim × dim block. Nothing is materialized at order dim^4.

That is not only cheaper. The previous implementation went through tomandel(SymmetricTensor{order,dim}(A)), and building a SymmetricTensor from a full component array makes Tensors validate the symmetry — a check that cannot be answered on a Symbolics.Num and therefore concluded "not symmetric", so KM of a symbolic isotropic tensor threw an InexactError. Displaying one did too, show going through KM.

julia
KM(t::TensTI{4})

Kelvin-Mandel (6×6) matrix of the Walpole tensor.

julia
KM(t::TensOrtho)

Returns the 6×6 Kelvin-Mandel matrix in the canonical frame. Use KM_material(t) for the block-diagonal form in the material frame.

julia
KM(t::TensCubic)

The 6×6 Kelvin-Mandel matrix in the canonical frame. Use KM_material for the sparse form in the cube frame.

TensND.inv_KM Function
julia
inv_KM(v::AbstractVecOrMat; kwargs...)

Define a tensor from a Kelvin-Mandel vector or matrix representation

TensND.diff_with_basis Function
julia
diff_with_basis(t::AbstractTens, args...; kwargs...)

Differentiate t including its basis, then re-express the result on the original basis and variance.

This differs from applied componentwise: when the basis itself depends on the differentiation variable — as a rotated or curvilinear frame does — the derivative of the basis vectors contributes, and dropping it is the classic way to lose the Christoffel terms.

TensND.tens_basis Function
julia
tens_basis(ℬ::AbstractBasis, i::Integer, var = :cov)  AbstractTens{1}
tens_basis(ℬ::AbstractBasis, var = :cov)  NTuple

The i-th basis vector of as a first-order tensor, or all of them as a tuple. var = :cov gives the basis vectors 𝐞ᵢ, var = :cont the dual vectors 𝐞ⁱ.