Bases, variance and the metric
The one notion TensND has that a Cartesian-only tensor library does not: a tensor carries a basis, which need not be orthonormal, and a variance saying how each index transforms. This tutorial builds an oblique basis, moves components between variances, and checks the identities of Bases and variance — symbolically and numerically.
The definitions in one place: for a basis
using TensND
using LinearAlgebra
using SymPy
using TensorsAn oblique basis
The constructor takes the new basis vectors as columns expressed in the canonical basis. Nothing here is orthogonal or normalized.
ℬ = Basis(Sym[1 0 0; 0 1 0; 0 1 1])The four stored matrices. vecbasis(ℬ, :cov) holds the vecbasis(ℬ, :cont) the dual vectors
vecbasis(ℬ, :cov)vecbasis(ℬ, :cont)The covariant metric
metric(ℬ, :cov)metric(ℬ, :cont)The defining identities
tsimplify(metric(ℬ, :cont) * metric(ℬ, :cov))tsimplify(transpose(vecbasis(ℬ, :cont)) * vecbasis(ℬ, :cov))The basis is neither orthogonal nor orthonormal, which is exactly why variance will matter below:
isorthogonal(ℬ), isorthonormal(ℬ)(false, false)Covariant and contravariant components of a vector
The same vector, two sets of components. TensND stores a tensor together with its basis and variance, and components converts.
V = Tens(Tensor{1, 3}(i -> symbols("v$i", real = true)))Contravariant components
components(V, ℬ, (:cont,))Covariant components
components(V, ℬ, (:cov,))They are related by the metric,
tsimplify(metric(ℬ, :cov) * components(V, ℬ, (:cont,)) - components(V, ℬ, (:cov,)))Order 2: four variance combinations
An order-
T = Tens(Tensor{2, 3}((i, j) -> symbols("t$i$j", real = true)))components(T, ℬ, (:cov, :cov))tfactor(tsimplify(components(T, ℬ, (:cont, :cov))))The cheapest sanity check on a basis
Store the covariant metric as a twice-covariant tensor and raise one index. Since
G = Tens(metric(ℬ, :cov), ℬ, (:cov, :cov))
components(G, (:cont, :cov))Normalization removes the scaling, not the obliquity
Dividing each vector by its norm gives unit vectors, so the metric acquires a unit diagonal — but the off-diagonal terms, which measure the angles between the vectors, survive. Variance still matters.
ℬ̄ = normalize(ℬ)
metric(ℬ̄, :cov)isorthogonal(ℬ̄), isorthonormal(ℬ̄)(false, false)Where variance becomes invisible
On an orthonormal basis CanonicalBasis and RotatedBasis, and it is why tensors on those bases carry no variance tuple at all.
θ, ϕ, ψ = symbols("θ ϕ ψ", real = true)
ℬʳ = Basis(θ, ϕ, ψ)
typeof(ℬʳ)RotatedBasis{3, Sym{PyCall.PyObject}}isorthonormal(ℬʳ), tsimplify(metric(ℬʳ, :cov))(true, Sym{PyCall.PyObject}[1 0 0; 0 1 0; 0 0 1])The same vector expressed on a rotated basis: covariant and contravariant components are equal.
W = Tens(Tensor{1, 3}(i -> symbols("w$i", real = true)))
tsimplify(components(W, ℬʳ, (:cov,)) - components(W, ℬʳ, (:cont,)))Numerically, on an orthogonal (scaled) basis
An OrthogonalBasis is an orthonormal basis with a scaling factor
ℬ₀ = Basis(0.3, 0.7, 0.0) # orthonormal, rotated
χ = (2.0, 3.0, 0.5) # scaling factors
ℬχ = Basis(ℬ₀, χ)
typeof(ℬχ)OrthogonalBasis{3, Float64}round.(metric(ℬχ, :cov), digits = 12)3×3 LinearAlgebra.Diagonal{Float64, Vector{Float64}}:
4.0 ⋅ ⋅
⋅ 9.0 ⋅
⋅ ⋅ 0.25Diagonal, with entries
round.(diag(metric(ℬχ, :cov)) .- [χ[i]^2 for i in 1:3], digits = 14)3-element Vector{Float64}:
0.0
0.0
0.0and the dual vectors are
round.(
vecbasis(ℬχ, :cont) .- vecbasis(ℬχ, :cov) * diagm([1 / χ[i]^2 for i in 1:3]),
digits = 14,
)3×3 Matrix{Float64}:
0.0 -0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0Summary
| Basis type | metric | variance matters? |
|---|---|---|
CanonicalBasis | no | |
RotatedBasis | no | |
OrthogonalBasis | yes, diagonally | |
Basis | full | yes |
The constructor always returns the most specific type that applies, so the cost of a component conversion is decided by the basis, not by the caller.
This page was generated using Literate.jl.