Tensors
The tensor hierarchy, component conversion and the Kelvin-Mandel map. Theory: Tensor algebra; usage: Tensors.
TensND.AbstractTens Type
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:
| Family | Types | Stores |
|---|---|---|
| general | Tens, TensRotated, TensCanonical, TensOrthogonal | a full component array |
| structured | TensISO, TensTI, TensOrtho | a 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
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.TensororSymmetricTensor)a basis of
AbstractBasistypea tuple of variances (covariant
:covor contravariant:cont) of length equal to theorderof 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> ℬ = 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 1See also components, change_tens, get_var.
TensND.components Function
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> ℬ = 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)/2TensND.components_canon Function
components_canon(t::AbstractTens)Extract the components of a tensor in the canonical basis
TensND.change_tens Function
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> ℬ = 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
change_tens_canon(t::AbstractTens{order,dim,T},var::NTuple{order,Symbol})Rewrite the same tensor with components corresponding to the canonical basis
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_basis Function
get_basis(t::AbstractTens) → AbstractBasisBasis 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
get_var(t::AbstractTens) → NTuple{order,Symbol}
get_var(t::AbstractTens, i::Integer) → SymbolVariance 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
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
get_array(t::TensTI{4, T}) → Array{T,4}Compute the 3×3×3×3 component array from the Walpole coefficients and axis.
get_array(t::TensTI{2,T,2}) → Array{T,2}Compute the 3×3 component array: a*(δᵢⱼ − nᵢnⱼ) + b*nᵢnⱼ.
Examples
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.0get_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
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> 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> LAPLACE(SymFunction("f", real = true)(r)) |> pprint
d
2 2⋅──(f(r))
d dr
───(f(r)) + ──────────
2 r
drTensor 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
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> σ = 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₁₁₃₃ √2⋅C₁₁₃₂ √2⋅C₁₁₃₁ √2⋅C₁₁₂₁
C₂₂₁₁ C₂₂₂₂ C₂₂₃₃ √2⋅C₂₂₃₂ √2⋅C₂₂₃₁ √2⋅C₂₂₂₁
C₃₃₁₁ C₃₃₂₂ C₃₃₃₃ √2⋅C₃₃₃₂ √2⋅C₃₃₃₁ √2⋅C₃₃₂₁
√2⋅C₃₂₁₁ √2⋅C₃₂₂₂ √2⋅C₃₂₃₃ 2⋅C₃₂₃₂ 2⋅C₃₂₃₁ 2⋅C₃₂₂₁
√2⋅C₃₁₁₁ √2⋅C₃₁₂₂ √2⋅C₃₁₃₃ 2⋅C₃₁₃₂ 2⋅C₃₁₃₁ 2⋅C₃₁₂₁
√2⋅C₂₁₁₁ √2⋅C₂₁₂₂ √2⋅C₂₁₃₃ 2⋅C₂₁₃₂ 2⋅C₂₁₃₁ 2⋅C₂₁₂₁KM(A::TensISO{2,dim}) -> Vector
KM(A::TensISO{4,dim}) -> MatrixKelvin-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.
KM(t::TensTI{4})Kelvin-Mandel (6×6) matrix of the Walpole tensor.
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.
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
inv_KM(v::AbstractVecOrMat; kwargs...)Define a tensor from a Kelvin-Mandel vector or matrix representation
TensND.diff_with_basis Function
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
tens_basis(ℬ::AbstractBasis, i::Integer, var = :cov) → AbstractTens{1}
tens_basis(ℬ::AbstractBasis, var = :cov) → NTupleThe 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 𝐞ⁱ.