A SubManifoldSym is a hypersurface embedded in : one coordinate fewer than the ambient space, with the unit normal completing the frame. It carries both geometries — the metric it inherits, and the way it bends inside the ambient space.
Surface indices run from to and are written ; ambient indices run to and are written .
SymPy cannot know that lies in : declaring the symbol real, or even positive, still leaves irreducible, and every curvature comes back multiplied by — correct, but unreadable. The rules argument of SubManifoldSym is exactly the mechanism for this: a rewrite applied after each simplification.
julia
R = symbols("R", positive = true)θ = symbols("θ", positive = true)ϕ = symbols("ϕ", real = true)z = symbols("z", real = true)
One principal curvature vanishes, so : the cylinder is intrinsically flat and can be unrolled onto a plane without distortion, although it is visibly curved in space. This is Gauss's Theorema Egregium in one line.
The closed forms are large, so they are more usefully read as values. At the apex the surface osculates a sphere of radius , giving and ; both fall off as one moves outwards.
Note the sign: the parametrization orients the normal towards the concave side, opposite to the outward normal used for the sphere above, so comes out positive here where the sphere gave . The Gaussian curvature , being a determinant, is insensitive to the orientation.
julia
println(" s = √(x²+y²) K·R² H·R")for s in (0.0, 0.5, 1.0, 2.0, 4.0) sub = Dict(x => Sym(s), y => Sym(0), R => Sym(1)) Kv = Float64(simplify(pb.K.subs(sub))) Hv = Float64(simplify(pb.H.subs(sub))) println(" ", rpad(s, 12), rpad(round(Kv, digits = 8), 15), round(Hv, digits = 8))end
Because a SubManifoldSym is a coordinate system, the differential operators apply to fields defined on the surface. The identity tying the two geometries together is
julia
G = GRAD(normal(Sphere), Sphere)
Compare tensors, not stored arrays
The two sides are stored on different bases and variances, so subtracting their get_arrays gives a nonzero — and rather convincing — wrong answer. On a sphere of radius the raw arrays differ by a factor , so the discrepancy even vanishes for .
Brought to a common basis and variance the difference is exactly zero, in every variance:
julia
ℬnat = natural_basis(Sphere)for var in ((:cov, :cov), (:cont, :cov), (:cov, :cont), (:cont, :cont)) resid = tsimplify.(components(G, ℬnat, var) + components(𝐛, ℬnat, var)) println(" variance ", var, " : all zero ? ", all(iszero, resid))end
variance (:cov, :cov) : all zero ? true variance (:cont, :cov) : all zero ? true variance (:cov, :cont) : all zero ? true variance (:cont, :cont) : all zero ? true
This is the practical face of Bases and variance: a tensor is not its component array, and only components in the same basis may be compared.
connection returns the connection coefficients of the induced metric — Christoffel symbols, never a Riemann curvature tensor. (The accessor used to be called Riemann, which named the wrong object.) For the sphere:
They are not tensor components: they change inhomogeneously under a change of chart and can be made to vanish at any single point, which no curvature can. The intrinsic curvature is the computed above from and .