Projection
Closest tensor of a prescribed material symmetry, and symmetry detection. Theory: Projection onto a symmetry class; usage: Projection.
TensND.project Function
project(A::AbstractArray{T,4}, ::Val{:TI}, n) → (TensTI{4, T, 5}, d, drel)Project a 4th-order tensor A (3×3×3×3) onto the transversely isotropic subspace with fixed symmetry axis n. Returns a major-symmetric TensTI{4, T, 5}.
The projection minimizes the Frobenius distance ‖B − A‖ over all TI tensors B with axis n.
Returns a 3-tuple (B, d, drel):
B: the projectedTensTI{4, T, 5}d: absolute Frobenius distance‖B − A‖drel: relative distanced / ‖A‖
Examples
julia> n = [0., 0., 1.];
julia> C = ti_from_constants(10., 3., 2.5, 12., 2., n);
julia> B, d, drel = project(get_array(C), :TI, n);
julia> d < 1e-12
true
julia> ti_constants(B) == ti_constants(C)
trueproject(A::AbstractArray{T,2}, ::Val{:TI}, n) → (TensTI{2,T,2}, d, drel)Project a 2nd-order tensor A (3×3) onto the transversely isotropic subspace with fixed symmetry axis n. Returns a TensTI{2,T,2}.
In the rotated frame where e₃ = n:
a = (M[1,1] + M[2,2]) / 2(transverse)b = M[3,3](axial)
Examples
julia> n = [0., 0., 1.];
julia> A = [5. 0 0; 0 5 0; 0 0 8];
julia> B, d, drel = project(A, :TI, n);
julia> B.data
(5.0, 8.0)project(A::AbstractArray{T,4}, ::Val{:ORTHO}, frame::OrthonormalBasis{3}) → (TensOrtho{4,T,9}, d, drel)Project a 4th-order tensor A (3×3×3×3) onto the orthotropic subspace with fixed material frame frame. Returns a TensOrtho{4,T,9}.
Examples
julia> frame = CanonicalBasis{3,Float64}();
julia> t = TensOrtho(10., 8., 12., 3., 2.5, 1.5, 2., 3., 3.5, frame);
julia> B, d, drel = project(get_array(t), :ORTHO, frame);
julia> d < 1e-12
trueproject(A::AbstractArray{T,4}, ::Val{:CUBIC}, frame::OrthonormalBasis{3}) → (TensCubic{T}, d, drel)Project a 4th-order tensor onto the cubic subspace with cube axes frame.
Read drel rather than trusting the projection. Where the morphology and the medium leave the octahedral group invariant, the answer belongs to the class by group theory, so its distance to it is discretization error and nothing else — an error estimate that costs nothing and involves no reference solution. Where they do not, drel says how much was discarded, and the projection is a fit like any other.
Examples
julia> frame = CanonicalBasis{3,Float64}();
julia> t = cubic_from_constants(10.0, 4.0, 2.0, frame);
julia> B, d, drel = project(get_array(t), :CUBIC, frame);
julia> d < 1e-12
trueSee also best_fit_cubic, cubic_anisotropy.
project(A::AbstractArray{T,2}, ::Val{:CUBIC}, frame) → (TensISO{2,3,T}, d, drel)At order two the cubic class is the isotropic class, the octahedral group leaving no second-order tensor invariant but a multiple of the identity. The frame is accepted and ignored, and the isotropic projection is returned.
That is not a shortcut: it is why a cube-symmetric pore has a single scalar resistivity contribution while its compliance contribution needs three constants, and why a conduction computation on such a shape carries no anisotropy signal whatever the shape does in elasticity.
project(A::AbstractArray{T,2}, ::Val{:ORTHO}, frame::OrthonormalBasis{3}) → (TensOrtho{2,T,3}, d, drel)Project a 2nd-order tensor A (3×3) onto the orthotropic subspace with fixed material frame frame. The projection is diag(M₁₁, M₂₂, M₃₃) in the material frame, returned as a TensOrtho{2} on that frame (as the TI projection returns a TensTI{2}).
Examples
julia> frame = CanonicalBasis{3,Float64}();
julia> A = [5. 1 2; 1 8 3; 2 3 12];
julia> B, d, drel = project(A, :ORTHO, frame);
julia> get_array(B) ≈ diagm([5., 8., 12.])
trueproject(A::AbstractArray, sym::Symbol, arg) → (projected, d, drel)Convenience dispatch: project(A, :TI, n) calls project(A, Val(:TI), n).
project(A::AbstractArray{T,4}, ::Val{:TI}) where {T<:AbstractFloat}Find the best TI approximation of A over all symmetry axes. The axis is searched — the objective screened on 200 axes, Newton's method on the angles from the best ones, from the eigenstructure candidate and from e₃ — with no random step, so a call is repeatable to the bit.
See also project(A, ::Val{:TI}, n) for fixed-axis projection.
project(A::AbstractArray{T,2}, ::Val{:TI}) where {T<:AbstractFloat}Find the best TI approximation of a 2nd-order tensor A over all symmetry axes. No search is needed: the axis is an eigenvector of the smallest or the largest eigenvalue of the symmetric part of A, whichever gives the closer tensor.
project(A::AbstractArray{T,4}, ::Val{:ORTHO}) where {T<:AbstractFloat}Find the best orthotropic approximation of A over all material frames. The frame is searched as the TI axis is (400 frames screened, Newton's method on the best ones, on the eigenstructure candidate and on the canonical frame).
project(A::AbstractArray{T,4}, ::Val{:CUBIC}) where {T<:AbstractFloat}Find the best cubic approximation of A over all cube orientations, searched as an orthotropic frame is, but with no eigenstructure candidate, there being none for a cubic tensor.
The octahedral group is discrete, but the orientation of the cube is not: it is an ordinary rotation, so the objective is smooth in the Euler angles exactly as it is for a TI axis or an orthotropic frame. The only trace the group leaves is that the optimum is 24-fold degenerate — the optimizer returns one of the 24 equivalent frames, and they all describe the same tensor with the same coefficients (see TensCubic).
See also project(A, ::Val{:CUBIC}, frame) for fixed-frame projection.
project(A::AbstractArray{T,2}, ::Val{:CUBIC})At order two the cubic class is the isotropic class, which has no orientation to optimize, so this is the isotropic projection, for any element type the isotropic one takes.
project(A::AbstractArray{T,2}, ::Val{:ORTHO}) where {T<:AbstractFloat}Find the best orthotropic approximation of a 2nd-order tensor A over all material frames. No search is needed: it is the eigenframe of the symmetric part of A, in which the projection keeps all of that part.
project(t, ::Val{:TI6}, n) → (TensTI{4,T,6}, d, drel)Projection of a fourth-order tensor with the minor symmetries onto the transversely isotropic tensors about n without major symmetry: the six Walpole coefficients, ℓ₃ ≠ ℓ₄ allowed — the class of a strain or flux concentration tensor, where project(A, Val(:TI), …) returns the major-symmetric five. It is the exact average about n (transverse_isotropify) without its two coefficients odd in n (ℓ₇, ℓ₈), which a reflection through the plane normal to n reverses: the orthogonal projection onto the tensors invariant under that reflection too. d is the Frobenius distance to t, drel its ratio to ‖t‖.
TensND.best_sym_tens Function
best_sym_tens(t; proj=(:ISO, :CUBIC, :TI, :ORTHO), ε=1e-6, optimize_angles=false)Find the best (most restrictive) symmetry of tensor t by trying each symmetry class in proj (from most to least symmetric) and accepting the first whose relative projection error is below ε.
optimize_angles=false(default, cheap path, no optimization): the:ISOprojection is closed-form; for:TIthe symmetry axis is taken fromtitself (if it is a structured TI container) or derived from the Kelvin-Mandel eigenstructure (otherwise); for:ORTHOthe material frame is taken fromtor derived likewise.optimize_angles=true: the:TIaxis and the:ORTHOand:CUBICframes are searched byproject(a screening of fixed orientations, then Newton's method on the best ones).
Returns (projected, d, drel, sym) where sym ∈ {:ISO, :CUBIC, :TI, :ORTHO, :ANISO}.
Examples
julia> n = [0., 0., 1.];
julia> C = ti_from_constants(10., 3., 2.5, 12., 2., n);
julia> _, _, _, sym = best_sym_tens(C);
julia> sym === :TI
trueSee also best_sym_tens(t, n_or_frame) for fixed-axis/frame use, project.
best_sym_tens(t, n_or_frame; proj=(:ISO, :CUBIC, :TI, :ORTHO), ε=1e-6)Find the best symmetry of tensor t with a fixed symmetry axis n (for TI) or material frame frame (for ORTHO). No rotation optimization is performed.
n_or_frame: a vector (axis for TI) orOrthonormalBasis{3}(frame for ORTHO and CUBIC). For ISO projection the extra argument is ignored.
Because there is one argument for two kinds of orientation, the classes it cannot serve are dropped from proj: an axis leaves :ISO and :TI, a frame leaves :ISO, :CUBIC and :ORTHO. Pass the kind that matches the classes you mean to test.
Returns (projected, d, drel, sym).
Examples
julia> n = [0., 0., 1.];
julia> C = ti_from_constants(10., 3., 2.5, 12., 2., n);
julia> _, _, drel, sym = best_sym_tens(C, n);
julia> sym == :TI && drel < 1e-12
truebest_sym_tens(t; proj=(:ISO, :CUBIC, :TI, :ORTHO), ε=1e-6, optimize_angles=false)Find the best (most restrictive) symmetry of tensor t by trying each symmetry class in proj (from most to least symmetric) and accepting the first whose relative projection error is below ε.
optimize_angles=false(default, cheap path, no optimization): the:ISOprojection is closed-form; for:TIthe symmetry axis is taken fromtitself (if it is a structured TI container) or derived from the Kelvin-Mandel eigenstructure (otherwise); for:ORTHOthe material frame is taken fromtor derived likewise.optimize_angles=true: the:TIaxis and the:ORTHOand:CUBICframes are searched byproject(a screening of fixed orientations, then Newton's method on the best ones).
Returns (projected, d, drel, sym) where sym ∈ {:ISO, :CUBIC, :TI, :ORTHO, :ANISO}.
Examples
julia> n = [0., 0., 1.];
julia> C = ti_from_constants(10., 3., 2.5, 12., 2., n);
julia> _, _, _, sym = best_sym_tens(C);
julia> sym === :TI
trueSee also best_sym_tens(t, n_or_frame) for fixed-axis/frame use, project.
best_sym_tens(t, n_or_frame; proj=(:ISO, :CUBIC, :TI, :ORTHO), ε=1e-6)Find the best symmetry of tensor t with a fixed symmetry axis n (for TI) or material frame frame (for ORTHO). No rotation optimization is performed.
n_or_frame: a vector (axis for TI) orOrthonormalBasis{3}(frame for ORTHO and CUBIC). For ISO projection the extra argument is ignored.
Because there is one argument for two kinds of orientation, the classes it cannot serve are dropped from proj: an axis leaves :ISO and :TI, a frame leaves :ISO, :CUBIC and :ORTHO. Pass the kind that matches the classes you mean to test.
Returns (projected, d, drel, sym).
Examples
julia> n = [0., 0., 1.];
julia> C = ti_from_constants(10., 3., 2.5, 12., 2., n);
julia> _, _, drel, sym = best_sym_tens(C, n);
julia> sym == :TI && drel < 1e-12
trueTensND.isisotropic Function
isisotropic(A::AbstractArray; ε=1e-6) → BoolReturn true when the components of A satisfy material isotropy up to relative tolerance ε (closed-form projection: no optimization).
The value-level predicate complements the type-level isisotropic(::TensISO) = true defined in tens_isotropic.jl: the type version asks whether the container imposes isotropy, this version asks whether the numerical components satisfy it.
Examples
julia> C = [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0];
julia> isisotropic(C)
true
julia> isisotropic([1.0 2.0 0.0; 2.0 3.0 0.0; 0.0 0.0 1.0])
falseTensND.istransverselyisotropic Function
istransverselyisotropic(A)Return true if A is a TensTI{4}, indicating transverse isotropy.
istransverselyisotropic(A::AbstractArray, n; ε=1e-6) → BoolReturn true when A is transversely isotropic about the given axis n up to relative tolerance ε (closed-form projection on the fixed axis).
Examples
julia> n = [0.0, 0.0, 1.0];
julia> C = ti_from_constants(10., 3., 2.5, 12., 2., n);
julia> istransverselyisotropic(get_array(C), n)
true
julia> istransverselyisotropic(get_array(C), [1.0, 0.0, 0.0])
falseistransverselyisotropic(A::AbstractArray; ε=1e-6, optimize_angles=false) → BoolCheap default: propose a candidate TI axis from the Kelvin-Mandel eigendecomposition of A and check the residual. With optimize_angles=true, searches the axis (project(A, :TI)).
See also istransverselyisotropic(A, n), _candidate_TI_axis.
TensND.isorthotropic Function
isorthotropic(A::AbstractArray, frame::OrthonormalBasis{3}; ε=1e-6) → BoolReturn true when A is orthotropic in the given material frame up to relative tolerance ε (closed-form projection on the fixed frame).
isorthotropic(A::AbstractArray; ε=1e-6, optimize_angles=false) → BoolCheap default: propose a candidate orthotropic frame from the Kelvin-Mandel eigendecomposition and check the residual. With optimize_angles=true, searches the frame (project(A, :ORTHO)).
TensND.iscubic Function
iscubic(t) -> BoolWhether t is stored as a TensCubic. A type-level query, like isisotropic and istransverselyisotropic: it says what the container guarantees, not whether the components happen to satisfy a tighter symmetry.
iscubic(A::AbstractArray, frame::OrthonormalBasis{3}; ε = 1.0e-6) -> BoolWhether A is cubic about frame to within ε, measured as the relative Frobenius distance to the class.
The value-level companion of the type-level iscubic(::TensCubic), exactly as for isisotropic, istransverselyisotropic and isorthotropic: the first asks what the components say, the second what the container guarantees.
iscubic(A::AbstractArray; ε = 1.0e-6) -> BoolWhether A is cubic about some cube, the cube orientation being searched (project(A, :CUBIC)). Unlike istransverselyisotropic and isorthotropic it has no cheap alternative: every second-order contraction of a cubic tensor is isotropic, so no eigenframe reveals where the cube points and there is nothing to start from but a search.
Give the frame — iscubic(A, frame) — whenever it is known; that path is exact and costs one projection.
TensND.ti_params_from_mandel Function
ti_params_from_mandel(C::AbstractMatrix) → (ℓ₁, ℓ₂, ℓ₃, ℓ₅, ℓ₆)Project a 6×6 Kelvin-Mandel matrix (in a frame where e₃ is the TI symmetry axis) onto the major-symmetric TI subspace, returning the 5 Walpole coefficients. See project for the tensor-level equivalent.
TensND.mandel_from_ti_params Function
mandel_from_ti_params(ℓ₁, ℓ₂, ℓ₃, ℓ₅, ℓ₆) → SMatrix{6,6}Build a 6×6 Kelvin-Mandel matrix (in the frame where e₃ is the TI axis) from 5 major-symmetric Walpole coefficients — the reciprocal of ti_params_from_mandel.
TensND.ti2_params_from_matrix Function
ti2_params_from_matrix(M::AbstractMatrix) → (a, b, c)The transversely isotropic part of a 3×3 matrix written in a frame whose third axis is the symmetry axis: the transverse coefficient a = (M₁₁ + M₂₂)/2, the axial one b = M₃₃ and the in-plane antisymmetric one c = (M₂₁ − M₁₂)/2, those of a (𝟏 − n⊗n) + b n⊗n + c [n]ₓ (see TensTI{2}). For a symmetric matrix c = 0. The order-2 counterpart of ti_params_from_mandel.
TensND.ortho_params_from_mandel Function
ortho_params_from_mandel(C::AbstractMatrix) → NTuple{9}Extract the 9 orthotropic parameters (C₁₁,C₂₂,C₃₃,C₁₂,C₁₃,C₂₃,C₄₄,C₅₅,C₆₆) from a 6×6 Kelvin-Mandel matrix in the material frame. See project for the tensor-level equivalent.
TensND.mandel_from_ortho_params Function
mandel_from_ortho_params(C₁₁, C₂₂, C₃₃, C₁₂, C₁₃, C₂₃, C₄₄, C₅₅, C₆₆) → SMatrix{6,6}Build a 6×6 Kelvin-Mandel matrix in the material frame from 9 orthotropic parameters — the reciprocal of ortho_params_from_mandel.
TensND.cubic_params_from_mandel Function
cubic_params_from_mandel(C::AbstractMatrix) → (α, β, γ)Extract the three cubic coefficients on (𝕁, 𝔼, 𝕋) from a 6×6 Kelvin-Mandel matrix in the cube frame. See project for the tensor-level equivalent, and cubic_constants for the engineering constants.
TensND.mandel_from_cubic_params Function
mandel_from_cubic_params(α, β, γ) → SMatrix{6,6}Build a 6×6 Kelvin-Mandel matrix in the cube frame from the three cubic coefficients — the reciprocal of cubic_params_from_mandel.
TensND.best_fit_iso Function
best_fit_iso(t::AbstractTens) -> TensISOOrthogonal (Frobenius) projection of t onto span(𝕁, 𝕂) at order 4, onto span(𝟏) at order 2 — the projection component of project(Val(:ISO), t).
For a minor-symmetric tensor this coincides with the exact SO(3) average isotropify; for a general one it does not, and the average is the meaningful object.
TensND.best_fit_ti Function
best_fit_ti(t::AbstractTens{4,3}, axis) -> TensTI{4,T,5}
best_fit_ti(t::AbstractTens{2,3}, axis) -> TensTI{2,T,2}Orthogonal (Frobenius) projection of t onto the major-symmetric Walpole span about axis — the projection component of project(Val(:TI), t, axis).
A projection, not an orientation average
It forces major symmetry, (ℓ₃+ℓ₄)/2, and drops the antisymmetric azimuthal couplings ℓ₇, ℓ₈. On a concentration tensor that content is physical and its loss is silent. Use transverse_isotropify to average, and this only to report parameters.
TensND.best_fit_ortho Function
best_fit_ortho(t::AbstractTens{4,3}, frame) -> TensOrtho
best_fit_ortho(t::AbstractTens{2,3}, frame) -> TensOrtho{2}Orthogonal (Frobenius) projection of t onto the orthotropic span in the given material frame — the projection component of project( Val(:ORTHO), t, frame). Same caveat as best_fit_ti: a fit, not an average.
TensND.best_fit_cubic Function
best_fit_cubic(t::AbstractTens{4,3}, frame) -> TensCubic
best_fit_cubic(t::AbstractTens{2,3}, frame) -> TensISO{2,3}Orthogonal (Frobenius) projection of t onto the cubic span with cube axes frame — the projection component of project(Val(:CUBIC), t, frame).
Unlike best_fit_ti, this loses nothing that a major-symmetric result would have kept: the cubic class is automatically major-symmetric, so there is no antisymmetric content to drop silently. What it does discard is everything outside the class, and project reports how much.
At order two it returns an isotropic tensor, the two classes coinciding there.
Exact rotation-group averages
A projection is a least-squares fit and drops whatever does not fit; a rotation-group average is exact and lossless on the invariant subspace. The two coincide only when the tensor already belongs to the class. Use the averages inside a computation — on a concentration or contribution tensor, which carries no major symmetry — and the fits only to report parameters.
TensND.isotropify Function
isotropify(A) → TensISOClosest isotropic tensor to A for the Frobenius distance, i.e. its orthogonal projection onto span(𝕁, 𝕂):
ISO(𝕋) = (𝕋 ⊙ 𝕁) 𝕁 + (𝕋 ⊙ 𝕂)/(𝕂 ⊙ 𝕂) 𝕂For an order-2 tensor this reduces to tr(A)/dim · 𝟏.
It does not commute with inversion
isotropify(inv(A)) ≠ inv(isotropify(A)): the Euclidean distance is not invariant under inversion, so projecting a stiffness and projecting the corresponding compliance give different isotropic materials. State which one was projected whenever a result is reported.
See also project, Isotropic tensors.
isotropify(t::AbstractTens{4,3}) → TensISO{4}Exact average of t over SO(3): α = T_iijj/3, β = (T_ijij − α)/5 → α𝕁 + β𝕂. Valid for minor-symmetric tensors with or without major symmetry (the isotropic subspace is {𝕁, 𝕂} in both cases), which is what distinguishes it from the AbstractArray method above — the latter is a Frobenius projection and the two coincide exactly on minor-symmetric input.
isotropify(t::AbstractTens{2,3}) → TensISO{2}Exact SO(3) average of a 2nd-order tensor: (tr t / 3) 𝟏.
TensND.transverse_isotropify Function
transverse_isotropify(t::AbstractTens{4,3}, n) → TensTI{4,T,8}Exact average of t over all rotations about the axis n ((1/2π)∫ R_φ ⋆ t dφ), for a minor-symmetric t with or without major symmetry. The result lives in the full 8-dimensional axially-invariant space: the non-major-symmetric components (ℓ₃ ≠ ℓ₄) and the antisymmetric azimuthal couplings (ℓ₇, ℓ₈) are preserved — they are dropped by naive symmetric TI projections but present in e.g. averaged strain-concentration tensors.
transverse_isotropify(t::AbstractTens{2,3}, n) → TensTI{2,T,3}Exact azimuthal average of a 2nd-order tensor about n: a·nT + b·nₙ + c·w with b = n̂ᵀ t n̂, a = (tr t − b)/2 and c = (w : t)/2 (w the in-plane rotation generator w·p = n̂ × p). The antisymmetric in-plane part c is preserved (a symmetric TI parametrization would silently drop it).
TensND.ti8_params_from_mandel Function
ti8_params_from_mandel(M::AbstractMatrix) → NTuple{8}Coefficients (ℓ₁, …, ℓ₈) of the exact azimuthal average about e₃ of a minor-symmetric tensor given by its 6×6 Kelvin-Mandel matrix (Tensors.jl index order 11, 22, 33, 23, 13, 12). Orthogonal projection onto the commutant algebra; TensND Walpole convention (ℓ₃ ↔ the C₃₃₁₁ side, ℓ₄ ↔ the C₁₁₃₃ side).
Two distinct uses, and the difference matters:
as an average, on a matrix that is not axially invariant — this is what
transverse_isotropifycalls;as an exact read-off, on a matrix already known to be axially invariant about
e₃, where the projection is the identity and the eight coefficients are simply recovered. This is the non-major-symmetric counterpart ofti_params_from_mandel, which projects onto the 5-coefficient major-symmetric span and would silently discardℓ₃ ≠ ℓ₄,ℓ₇andℓ₈— precisely the content a concentration tensor carries.
Reciprocal: mandel_from_ti8_params.
TensND.mandel_from_ti8_params Function
mandel_from_ti8_params(p::NTuple{8,T}) → Matrix{T} (6×6)Kelvin-Mandel matrix (Tensors order, axis e₃) of the axially-invariant tensor with coefficients (ℓ₁,…,ℓ₈). Inverse of ti8_params_from_mandel on the commutant subspace, and the 8-coefficient counterpart of mandel_from_ti_params.
TensND.ti_average_mandel Function
ti_average_mandel(M::AbstractMatrix, n) → Matrix (6×6)Exact azimuthal average about n of the minor-symmetric tensor whose 6×6 Kelvin-Mandel matrix (Tensors.jl order) is M, returned as a 6×6 matrix.
TensND.ti_average_matrix Function
ti_average_matrix(M::AbstractMatrix, n) → Matrix (3×3)Exact average about n of the second-order tensor whose canonical components are the 3×3 matrix M, returned as a matrix: a (𝟏 − n⊗n) + b n⊗n + c [n]ₓ, the in-plane antisymmetric part c kept (see transverse_isotropify). The order-2 counterpart of ti_average_mandel.
TensND.iso_average_mandel Function
iso_average_mandel(M::AbstractMatrix{T}) → (α, β)Exact SO(3) average of the minor-symmetric tensor whose Kelvin-Mandel matrix is M: α = (1/3)Σ_{i,j≤3} M[i,j], β = (tr M − α)/5. Returns the (α, β) coefficients of α𝕁 + β𝕂.