Skip to content

Riemannian Manifolds

Clustering on curved spaces

Not all data lives on a graph or in flat Euclidean space. Directions (wind, magnetic fields, gene expression on the unit sphere), geographic positions on the globe, or hierarchical structures embedded in hyperbolic space all live on curved Riemannian manifolds, where the meaningful notion of distance is the geodesic—the length of the shortest path along the surface, not the straight line through the ambient space.

kmeanssa-ng clusters directly on such manifolds using the exponential and logarithm maps that let cluster centers move along geodesics. These come from geomstats for a general manifold, or from built-in closed forms where they are known (the sphere, and the quotient Bolza surface), which avoid the geomstats per-call overhead.

Creating a manifold

Two factory functions return a RiemannianManifold ready to use:

from kmeanssa_ng import create_sphere, create_hyperbolic_space

sphere = create_sphere(2)  # the 2-sphere S^2, embedded in R^3
print(f"dim = {sphere.dim}, is_sphere = {sphere.is_sphere}")
dim = 2, is_sphere = True

Points are stored in extrinsic coordinates—for create_sphere(2), unit vectors in \(\mathbb{R}^3\).

Points and clustering

An observation on a manifold is a RiemannianPoint. Sample a few uniformly, then cluster them with the usual simulated annealing workflow—the only difference is that centers now perform Brownian motion and drift along great-circle geodesics, via the exp/log maps, with no ambient projection:

from kmeanssa_ng import (
    RiemannianPoint, SimulatedAnnealing, KMeansPlusPlus, MinimizeEnergy,
)

coords = sphere.random_uniform(150, random_state=0)
observations = [RiemannianPoint(sphere, x) for x in coords]

sa = SimulatedAnnealing(
    observations, k=3, lambda0=1.0, beta0=0.5, step_size=0.05, energy_mode="empirical"
)
centers = sa.run(KMeansPlusPlus(), MinimizeEnergy(), robust_prop=0.1)
print(f"Found {len(centers)} cluster centers on the sphere")
Found 3 cluster centers on the sphere

On a manifold, energy is always the empirical energy of the observations (energy_mode="empirical"), since there is no natural uniform distribution over a continuous surface to sum against.

Geodesic operations

A RiemannianManifold exposes the geometric primitives that drive the algorithm—and that the \(\varepsilon\)-net construction builds on. The logarithm map gives the tangent direction from one point to another, its norm is the geodesic distance, and the exponential map is its inverse:

import numpy as np

a, b = sphere.random_uniform(2, random_state=1)
tangent = sphere.log(a, b)                       # direction from a toward b
print(f"geodesic distance a->b = {float(sphere.norm(a, tangent)):.3f}")
print(f"exp(a, log(a, b)) == b: {np.allclose(sphere.exp(a, tangent), b)}")
geodesic distance a->b = 1.286
exp(a, log(a, b)) == b: True

Scaling up: mesh the manifold

Clustering directly on the manifold evaluates geodesics at every step. For larger problems it is faster—and theoretically grounded—to approximate the manifold once by a quantum graph and cluster on that graph instead. That is the subject of Meshing a Manifold.

Hyperbolic space

The hyperboloid model of hyperbolic space is available too, and the same geodesic operations apply:

hyperbolic = create_hyperbolic_space(2)
base = np.array([1.0, 0.0, 0.0])                 # the apex of the hyperboloid
tangent = np.array([0.0, 0.3, 0.4])              # a tangent direction there
print(f"geodesic step length = {float(hyperbolic.norm(base, tangent)):.3f}")
geodesic step length = 0.500

[!NOTE]

Hyperbolic space is non-compact. Uniform sampling and \(\varepsilon\)-net meshing currently target compact manifolds (the sphere and the like); support for bounded hyperbolic regions is a planned extension.