Skip to content

Structured tensors ​

<!– crosslinks: written by .github/scripts/crosslinks.py from docs/crosslinks.toml; do not edit –>

<!– /crosslinks –>

The compact symmetry-class types. Each stores a handful of scalars instead of components and computes products and inverses in closed form. The algebra behind each is on Isotropic tensors, The Walpole basis, The extended Walpole algebra, Orthotropy and Cubic symmetry.

Storage ​

Every class is parametrized the same way, by its order and the number N of stored coefficients; the invariance group decides which tensors a shape holds. The last three columns say what a product (⊡ at order 4, ⋅ at order 2) of two tensors of the shape on the same reference, an inverse and a major transpose return; a sum keeps the shape.

TypeOrderNStoredReferenceGroupProductInverseTranspose
TensISO{2,dim}21nonesamesameitself
TensISO{4,dim}42nonesamesameitself
TensCubic{T,B}43cube framesamesameitself
TensTI{2,T,2}22axis samesameitself
TensTI{2,T,3}23axis samesame  
TensTI{4,T,5}45axis N=6sameitself
TensTI{4,T,6}46axis samesame 
TensTI{4,T,8}48axis samesame ,   
TensOrtho{2,T,3}23framesamesameitself
TensOrtho{4,T,9}49frameN=12sameitself
TensOrtho{4,T,12}412 framesamesamenormal block transposed

The major-symmetric shapes are the ones whose product widens (N=5 → 6, N=9 → 12): their product is a product of two symmetric blocks. Tensors.majorsymmetric goes back (N=6, 8 → 5, N=12 → 9). The shapes hold the terms odd in that the mirrors of forbid; orthotropy has no such shapes, the rotations of and the full having the same invariant tensors at even order.

julia
using TensND, LinearAlgebra

iso = TensISO{3}(3 * 20.0, 2 * 8.0)      # 3k 𝕁 + 2μ 𝕂
TensISO{4, 3, Float64, 2}
→ decomposition: (60.0) 𝕁 + (16.0) 𝕂
→ KM: 6×6 Matrix{Float64}:
 30.6667  14.6667  14.6667   0.0   0.0   0.0
 14.6667  30.6667  14.6667   0.0   0.0   0.0
 14.6667  14.6667  30.6667   0.0   0.0   0.0
  0.0      0.0      0.0     16.0   0.0   0.0
  0.0      0.0      0.0      0.0  16.0   0.0
  0.0      0.0      0.0      0.0   0.0  16.0

The show method prints the algebraic form, not 81 numbers:

julia
n = (0.0, 0.0, 1.0)
ti = TensTI{4}(12.0, 13.0, 3.5, 7.0, 4.0, n)
TensTI{4, Float64, 5}
→ decomposition: (12.0) W₁ˢ + (13.0) W₂ˢ + (3.5) W₃ˢ + (7.0) W₄ˢ + (4.0) W₅ˢ
  axis n = (0.0, 0.0, 1.0)
→ KM: 6×6 Matrix{Float64}:
 10.0       3.0       2.47487  0.0  0.0  0.0
  3.0      10.0       2.47487  0.0  0.0  0.0
  2.47487   2.47487  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.0
julia
ℬ = CanonicalBasis{3, Float64}()
ort = TensOrtho(10.0, 8.0, 9.0, 3.0, 2.0, 4.0, 2.5, 3.0, 1.5, ℬ)
TensOrtho{4, Float64, 9, CanonicalBasis{3, Float64}}
→ decomposition: (10.0) P₁⊗P₁ + (8.0) P₂⊗P₂ + (9.0) P₃⊗P₃
  + (3.0)(P₁⊗P₂+P₂⊗P₁) + (2.0)(P₁⊗P₃+P₃⊗P₁) + (4.0)(P₂⊗P₃+P₃⊗P₂)
  + 2(2.5)(P₂⊠ˢP₃) + 2(3.0)(P₁⊠ˢP₃) + 2(1.5)(P₁⊠ˢP₂)
  frame: [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0]
→ KM (material frame): 6×6 StaticArraysCore.SMatrix{6, 6, Float64, 36} with indices SOneTo(6)×SOneTo(6):
 10.0  3.0  2.0  0.0  0.0  0.0
  3.0  8.0  4.0  0.0  0.0  0.0
  2.0  4.0  9.0  0.0  0.0  0.0
  0.0  0.0  0.0  5.0  0.0  0.0
  0.0  0.0  0.0  0.0  6.0  0.0
  0.0  0.0  0.0  0.0  0.0  3.0
→ KM (canonical frame): 6×6 Matrix{Float64}:
 10.0  3.0  2.0  0.0  0.0  0.0
  3.0  8.0  4.0  0.0  0.0  0.0
  2.0  4.0  9.0  0.0  0.0  0.0
  0.0  0.0  0.0  5.0  0.0  0.0
  0.0  0.0  0.0  0.0  6.0  0.0
  0.0  0.0  0.0  0.0  0.0  3.0
julia
cub = cubic_from_constants(10.0, 4.0, 2.0, ℬ)      # (C₁₁, C₁₂, C₄₄)
TensCubic{Float64, CanonicalBasis{3, Float64}}
→ decomposition: (18.0) 𝕁 + (6.0) 𝔼 + (4.0) 𝕋
  cube frame: [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0]
→ KM: 6×6 Matrix{Float64}:
 10.0   4.0   4.0  0.0  0.0  0.0
  4.0  10.0   4.0  0.0  0.0  0.0
  4.0   4.0  10.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  4.0

Constructors ​

ClassConstructors
ISOTensISO, tens_Id2, tens_Id4, tens_J4, tens_K4, iso_projectors
TI order 4TensTI, ti_from_constants, iso_to_ti, tens_W1…tens_W8, walpole_basis, walpole_basis_sym
TI order 2TensTI{2}(a, b, n) and (a, b, c, n)
CUBICTensCubic, cubic_from_constants, iso_to_cubic
ORTHO order 4TensOrtho{4}(C₁₁, …, C₆₆, frame) (nine constants) and (…, C₂₁, C₃₁, C₃₂, frame) (twelve), TensOrtho(KM, frame[, Val(12)]), iso_to_ortho, walpole_to_ortho, cubic_to_ortho
ORTHO order 2TensOrtho{2}(a₁, a₂, a₃, frame), iso_to_ortho, walpole_to_ortho

Accessors ​

FunctionApplies toReturns
get_dataallthe stored coefficient tuple
walpole_coeffsTensTI{4}the six classical Walpole coefficients
walpole_coeffsTensTI{4}all eight, zero-padded
axisTensTIthe symmetry axis
frameTensOrtho, TensCubicthe material or cube frame
referenceTensTI, TensOrtho, TensCubicaxis or frame, whichever applies
symmetryallthe class as a Symbol
mandel_materialTensOrtho, TensCubicthe sparse matrix in the class's own frame
cubic_constantsTensCubicthe constants
cubic_anisotropyTensCubicthe Zener-type departure from isotropy
julia
walpole_coeffs(ti), axis(ti)
((12.0, 13.0, 3.5, 3.5, 7.0, 4.0), (0.0, 0.0, 1.0))
julia
mandel_material(ort)
6×6 StaticArraysCore.SMatrix{6, 6, Float64, 36} with indices SOneTo(6)×SOneTo(6):
 10.0  3.0  2.0  0.0  0.0  0.0
  3.0  8.0  4.0  0.0  0.0  0.0
  2.0  4.0  9.0  0.0  0.0  0.0
  0.0  0.0  0.0  5.0  0.0  0.0
  0.0  0.0  0.0  0.0  6.0  0.0
  0.0  0.0  0.0  0.0  0.0  3.0

Closure: what an operation returns ​

This is the table to know. ⊡ between structured types stays structured only while the result stays in the class; otherwise it falls back to the generic route, exactly and without warning.

A ⊡ BISOCUBICTITITIORTHO
ISOISOCUBICTITITIORTHO
CUBICCUBICCUBICgenericgenericgenericORTHO
TI{4,5}TIgenericTITITIORTHO
TI{4,6}TIgenericTITITIORTHO
TI{4,8}TIgenericTITITIgeneric
ORTHOORTHOORTHOORTHOORTHOgenericORTHO

The CUBIC row and column need the two frames to describe the same cube, the iso_projectors/TI/ORTHO ones the same axis or frame, and a TI ⊡ ORTHO product a TI axis along one of the frame axes; otherwise the generic route is taken. ORTHO{12} stands for TensOrtho{4, T, 12}, whichever form the operands have.

Two widenings deserve an explanation, and both have the same cause: the major-symmetric tensors of a class do not form a subalgebra.

  • TI{4,5} ⊡ TI{4,5} → TI{4,6}. The Walpole product is a   matrix product, and     unless the blocks commute, so the product of two major-symmetric TI tensors generally has  .

  • ORTHO ⊡ ORTHO → ORTHO{12}. In the material frame the Kelvin-Mandel matrix is a   normal block plus three shear moduli, and the product of two symmetric blocks is not symmetric unless they commute: twelve independent constants where the major-symmetric form stores nine. The product is one   matrix product and three scalar ones, and inversion inverts the block. Tensors.majorsymmetric returns the nine-constant symmetric part.

CUBIC ⊡ CUBIC → CUBIC is the one entry that does not widen, and it is worth knowing why: each irreducible representation of the octahedral group appears with multiplicity one, so the three projectors are orthogonal, any two cubic tensors about the same cube commute, and the product is cubic and still major-symmetric. Cubic symmetry is the only class here that is a genuine commutative subalgebra. See Cubic symmetry.

CUBIC ⊡ TI is generic for the opposite reason: the two classes are incomparable, their intersection being the isotropic class.

At order 2, and between orders, the same rules hold on a shared reference:

OperationResult
ORTHO{2} ⋅ ORTHO{2}, with an ISO{2} or a coaxial TI{2,2}ORTHO{2}
ORTHO{4} ⊡ ORTHO{2}, ORTHO{2} ⊡ ORTHO{4}, the order-4 operand iso_projectors, CUBIC or a coaxial TI{4,5}/TI{4,6}ORTHO{2}: the normal block acting on
ORTHO{2} ⊗ ORTHO{2}ORTHO{4,12}; ORTHO{4,9} for  

as TI{4} ⊡ TI{2,2} → TI{2,2} and TI{2,2} ⊗ TI{2,2} → TI{4,6} (TI{4,5} for a self product).

Inversion, by contrast, always stays in the class:

julia
typeof(inv(iso)), typeof(inv(ti)), typeof(inv(ort)), typeof(inv(cub))
(TensISO{4, 3, Float64, 2}, TensTI{4, Float64, 5}, TensOrtho{4, Float64, 9, CanonicalBasis{3, Float64}}, TensCubic{Float64, CanonicalBasis{3, Float64}})

Blocks: a class as a sum of small matrices ​

Inside one class, in one frame, the double contraction of two tensors and the inverse of a tensor are computed on a few small matrices, the blocks of the class, each one independently of the others:

Classorder 4order 2
ISO,
CUBIC, , —
TI, , ,
ORTHOthe normal   block, , , , ,

The product of two tensors has, for blocks, the products of their blocks; the inverse has the inverses. symmetry_blocks reads the blocks of a tensor, from_symmetry_blocks builds the tensor back, taking the axis or the frame from a tensor of the class:

julia
bt = symmetry_blocks(ti)
([12.0 3.5; 3.5 13.0], [7.0;;], [4.0;;])
julia
from_symmetry_blocks(ti, map(inv, bt)) ≈ inv(ti)
true

This is what a computation that works on a whole family of tensors of one class needs. A relaxation operator of aging viscoelasticity, for instance, has a tensor coefficient for each pair of instants: on a grid of instants it is an     matrix for each block of size , instead of one   matrix, and its products and inverses are those of these smaller matrices. The order-2 classes play the same role for conduction or permittivity.

The orthotropic shear blocks are , not : the factor 2 is the one of the Kelvin-Mandel matrix, in which the product of tensors is the product of matrices.

The same blocks can be read on a matrix rather than on a tensor: a   Kelvin-Mandel matrix at order 4, a   matrix of components at order 2. symmetry_basis is the orthonormal basis, adapted to the class and to its axis or frame, in which such a matrix is block diagonal, and symmetry_layout says where each block sits (the isotropic shear modulus five times, the transverse shear twice, …). symmetry_blocks(A, M) reads the blocks of M in the class and frame of A, and symmetry_matrix builds the matrix back; a matrix that belongs to the class is rebuilt exactly, which is how one checks that it does:

julia
M = mandel(generic_tens(ti))
symmetry_blocks(ti, M)[1], symmetry_matrix(ti, symmetry_blocks(ti, M)) ≈ M
([12.0 3.499999999999999; 3.499999999999999 12.999999999999998], true)

Exact promotions ​

Moving up the lattice is exact — an isotropic tensor really is transversely isotropic about every axis:

julia
typeof(iso_to_ti(iso, [0.0, 0.0, 1.0]))
TensTI{4, Float64, 5}
julia
typeof(iso_to_ortho(iso, ℬ)), typeof(walpole_to_ortho(ti, ℬ, 3))
(TensOrtho{4, Float64, 9, CanonicalBasis{3, Float64}}, TensOrtho{4, Float64, 9, CanonicalBasis{3, Float64}})
julia
typeof(iso_to_cubic(iso, ℬ)), typeof(cubic_to_ortho(cub))
(TensCubic{Float64, CanonicalBasis{3, Float64}}, TensOrtho{4, Float64, 9, CanonicalBasis{3, Float64}})

Moving down is approximation, and is Projection.

Same axis, same frame ​

Addition and subtraction require the two operands to share their orientation — adding two orthotropic tensors expressed in different material frames throws:

julia
julia> ort + TensOrtho(6., 5., 7., 2., 1., 3., 1.2, 2.2, 0.8, Basis(0.3, 0.7, 0.2))
ERROR: ArgumentError: TensOrtho operation requires the same material frame

The assertion is deliberate: silently promoting to a generic tensor would hide a modeling error. Convert explicitly if that is what you mean.

TensCubic is the one class where the test is looser than equality, and legitimately so: the three projectors are invariant under the whole octahedral group, so two frames related by a signed permutation of the axes describe the same tensor with the same coefficients and are accepted. Permuting the axes of an orthotropic tensor permutes ; permuting those of a cubic one changes nothing.

When to use which ​

SituationType
the physics guarantees isotropyTensISO
a stiffness or compliance with an axis of symmetryTensTI{4,T,5}
an object with an axis but no major symmetry (a concentration tensor)TensTI{4,T,6} or {4,T,8}
a cubic crystal, a cubic array, a cube-symmetric shapeTensCubic
nine constants and a material frameTensOrtho{4,T,9}
an orthotropic object without major symmetry (a product, a concentration tensor)TensOrtho{4,T,12}
an order-2 tensor diagonal in a material frameTensOrtho{2,T,3}
anything elsea plain Tens

The measured cost of each is in Performance of the structured types. A tensor of any of these classes, with random coefficients and orientation, is drawn by rand (Random tensors).