Skip to content
The Lumenary

Math laboratory

Gaussian curvature from the metric, and Gauss-Bonnet

Computes with SymPy the Christoffel symbols and Gaussian curvature of a surface from its metric coefficients alone (the Theorema Egregium in action): round sphere K = 1, upper half-plane and Poincare disc K = -1, torus of revolution K = cos v / (r (R + r cos v)). Then integrates K dA over the sphere and the torus: 4 pi and 0, that is 2 pi chi.

Back to the labSymbolic computationExact computation
Gaussian curvature from the metric, and Gauss-BonnetPythonSymbolic computationExact computationComputationally tested
"""Curvature from the metric alone, and the Gauss-Bonnet theorem.

For a surface with metric g = E du^2 + 2F du dv + G dv^2, SymPy computes the
Christoffel symbols, the curvature tensor component R_1212 and the Gaussian
curvature K = R_1212 / det g, using nothing but derivatives of E, F, G: this
is Gauss's Theorema Egregium put to work. It treats the round sphere, the
hyperbolic upper half-plane, the Poincare disc and a torus of revolution, then
integrates K dA over the sphere and the torus and compares with 2*pi*chi.
Everything is exact symbolic computation for these specific metrics.
"""

import sympy

U, V = sympy.symbols("u v", real=True)

def christoffel(g):
    """Christoffel symbols Gamma[k][i][j] of a 2x2 metric matrix in (u, v)."""
    coords = (U, V)
    ginv = g.inv()
    gamma = [[[0, 0], [0, 0]] for _ in range(2)]
    for k in range(2):
        for i in range(2):
            for j in range(2):
                gamma[k][i][j] = sympy.simplify(sum(
                    ginv[k, m] * (sympy.diff(g[j, m], coords[i])
                                  + sympy.diff(g[i, m], coords[j])
                                  - sympy.diff(g[i, j], coords[m])) / 2
                    for m in range(2)))
    return gamma

def gaussian_curvature(g):
    """K = R_1212 / det g with R(d1, d2)d2 written via Christoffel symbols."""
    coords = (U, V)
    gam = christoffel(g)
    # R_1212 = <R(d_u, d_v) d_v, d_u> = sum over m of g_{um} R^m_{vuv}.
    def riem(m, i, j, k):
        """R^m_{ijk}: component of R(d_j, d_k) d_i on d_m."""
        val = sympy.diff(gam[m][k][i], coords[j]) - sympy.diff(gam[m][j][i], coords[k])
        val += sum(gam[m][j][p] * gam[p][k][i] - gam[m][k][p] * gam[p][j][i]
                   for p in range(2))
        return val
    r1212 = sum(g[0, m] * riem(m, 1, 0, 1) for m in range(2))
    return sympy.simplify(r1212 / g.det())

def integral_of_curvature(g, u_range, v_range):
    """The integral of K dA = K sqrt(det g) du dv over a coordinate rectangle."""
    density = sympy.simplify(gaussian_curvature(g) * sympy.sqrt(g.det()))
    return sympy.simplify(sympy.integrate(density, (U, *u_range), (V, *v_range)))

def main():
    big, small = sympy.Rational(2), sympy.Rational(1)
    sphere = sympy.Matrix([[1, 0], [0, sympy.sin(U) ** 2]])
    half_plane = sympy.Matrix([[1 / V**2, 0], [0, 1 / V**2]])
    disc_factor = 4 / (1 - U**2 - V**2) ** 2
    disc = sympy.Matrix([[disc_factor, 0], [0, disc_factor]])
    torus = sympy.Matrix([[(big + small * sympy.cos(V)) ** 2, 0], [0, small**2]])
    print("Gaussian curvature computed from the metric alone:")
    rows = [("round sphere  du^2 + sin(u)^2 dv^2", sphere),
            ("half-plane   (du^2 + dv^2)/v^2", half_plane),
            ("Poincare disc 4(du^2 + dv^2)/(1-u^2-v^2)^2", disc),
            ("torus (2 + cos v)^2 du^2 + dv^2", torus)]
    for name, g in rows:
        print(f"  {name:<44} K = {gaussian_curvature(g)}")
    gam = christoffel(half_plane)
    print("Half-plane Christoffel symbols (nonzero):")
    for k in range(2):
        for i in range(2):
            for j in range(i, 2):
                if gam[k][i][j] != 0:
                    print(f"  Gamma^{'uv'[k]}_{'uv'[i]}{'uv'[j]} = {gam[k][i][j]}")
    print()
    total_sphere = integral_of_curvature(sphere, (0, sympy.pi), (0, 2 * sympy.pi))
    total_torus = integral_of_curvature(torus, (0, 2 * sympy.pi),
                                        (0, 2 * sympy.pi))
    print(f"Integral of K dA over the sphere: {total_sphere}  (2 pi chi = 4 pi)")
    print(f"Integral of K dA over the torus:  {total_torus}  (2 pi chi = 0)")
    print()
    print("Epistemic status: exact symbolic computation for four explicit metrics")
    print("and two integrals. It illustrates the Theorema Egregium and checks the")
    print("Gauss-Bonnet theorem in two cases; the theorem itself is proved in the")
    print("lesson.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

Used in