Code for the paper "Learning Tangent Bundles and Characteristic Classes with Autoencoder Atlases" by Eduardo Paluzo-Hidalgo and Yuichi Ike (Preprint ArXiV).
This repository implements autoencoder atlases: collections of locally trained encoder, decoder pairs that form a learned atlas on a data manifold. Rather than computing a single global embedding, we train local autoencoders on overlapping chart domains and extract differential, topological invariants from the transition maps between charts.
The key theoretical insight is that reconstruction-consistent autoencoders automatically satisfy the cocycle condition, and linearizing their transition maps yields a vector bundle isomorphic to the tangent bundle. This gives direct access to characteristic classes, in particular the first Stiefel–Whitney class
Given a point cloud sampled from a manifold
-
Learns an atlas
$\mathcal{A} = {(U_i, E_i, D_i)}$ of local autoencoders on an open cover${U_i}$ . -
Extracts transition maps
$T_{ji} = E_j \circ D_i$ between overlapping charts. -
Computes the Jacobian sign cocycle
$\omega_{ji}(x) = sign(\det, d(T_{ji})_{E_i(x)})$ . -
Tests orientability by checking whether
$\omega$ is a coboundary in$C^1(\mathcal{U}; \mathbb{Z}/2)$ .
.
├── atlasautoencoder.py # Core library: LocalAutoencoder, AtlasAutoencoder, training, metrics, visualisation
├── orientability.py # Orientability detection: sign cocycle, coboundary test, connected component handling
├── sphere_good_cover.py # Good cover construction for S² (tetrahedral cover, stereographic projection)
├── Sphere.py # Experiment: S² (orientable, expected w₁ = 0)
├── mobius.py # Experiment: Möbius band (non-orientable, expected w₁ ≠ 0)
├── Klein_bottle.py # Experiment: Klein bottle in ℝ⁴ (non-orientable, expected w₁ ≠ 0)
├── line_patch_images.py # Experiment: ℝP² via line patch images (non-orientable, expected w₁ ≠ 0)
└── README.md
An autoencoder atlas on a manifold
-
${U_i}$ is an open cover of$M$ -
$E_i: U_i \to Z_i \subset \mathbb{R}^d$ is the encoder (local coordinates) -
$D_i: Z_i \to M$ is the decoder (inverse chart map) -
$D_i \circ E_i \approx Id_{U_i}$ (reconstruction consistency)
For overlapping charts
When reconstruction is exact (
This is not imposed as a regularisation term — it emerges algebraically from reconstruction consistency alone.
The linearised transition maps $g_{ji}(x) = d(T_{ji}){E_i(x)} \in GL_d(\mathbb{R})$ define a $GL_d(\mathbb{R})$-cocycle. When $d = \dim M$ and the atlas is compatible with the smooth structure, the resulting vector bundle $\mathcal{T}{\mathcal{A}}$ is isomorphic to the tangent bundle
The sign cocycle
This is the first Stiefel–Whitney class:
The code tracks three key quantities from the paper:
| Symbol | Name | Definition | Role |
|---|---|---|---|
| Reconstruction error | Approximation quality | ||
| Differential error | $\sup_x |d(D_i \circ E_i)x|{T_xM} - Id|_{\mathrm{op}}$ | Tangent map fidelity | |
| Non-degeneracy gap | Sign cocycle stability |
When
pip install tensorflow numpy matplotlib scipy scikit-learnpip install dreimac ripser persimpip install -r requirements.txtPython version: 3.9+ recommended. TensorFlow: 2.10+ required.
Each experiment script is self-contained and runs multiple trials with configurable random seeds:
# Orientable manifolds
python Sphere.py # S² — expects orientable (w₁ = 0)
# Non-orientable manifolds
python mobius.py # Möbius band — expects non-orientable (w₁ ≠ 0)
python Klein_bottle.py # Klein bottle — expects non-orientable (w₁ ≠ 0), requires dreimac
python line_patch_images.py # ℝP² line patches — expects non-orientable (w₁ ≠ 0), requires dreimac, ripserEach script outputs:
- A JSON file with full metrics from all trials (reconstruction error, differential error, non-degeneracy gap, orientability verdict, etc.)
- Summary statistics suitable for paper tables
- Diagnostic figures saved to disk
import numpy as np
from atlasautoencoder import AtlasAutoencoder, create_cover_from_neighborhoods
from orientability import check_orientability
# 1. Sample data from a manifold
theta = np.random.uniform(0, 2*np.pi, 1000)
phi = np.random.uniform(0, np.pi, 1000)
X = np.column_stack([
np.sin(phi)*np.cos(theta),
np.sin(phi)*np.sin(theta),
np.cos(phi)
])
# 2. Create an open cover
cover = create_cover_from_neighborhoods(X, n_charts=4, overlap_ratio=0.3)
# 3. Build and train the atlas
atlas = AtlasAutoencoder(
data=X,
n_charts=4,
subset_assignments=cover,
latent_dim=2,
hidden_dims=[32, 16]
)
atlas.fit(epochs=200, lambda_jac=0.01)
# 4. Inspect theoretical metrics
metrics = atlas.print_metrics_summary()
# Prints ε, η, δ, cocycle error, σ_min(dE)
# 5. Test orientability
result = check_orientability(atlas, X, cover)
print(f"Orientable: {result['orientable']}")AtlasAutoencoder — the central class.
| Method | Description |
|---|---|
fit(epochs, batch_size, lambda_jac, lambda_cocycle) |
Train all local autoencoders |
encode(x, chart) / decode(z, chart)
|
Encode/decode with a specific chart |
transition_map(z, i, j) |
Compute |
compute_transition_jacobians(i, j) |
Jacobian matrices of |
compute_determinant_signs(i, j) |
Signs of |
compute_all_metrics() |
Full |
print_metrics_summary() |
Formatted summary with stability diagnostics |
check_orientability — the main orientability detection function.
Takes a trained AtlasAutoencoder, the data, and the cover. Handles disconnected chart domains via DBSCAN clustering, computes the sign cocycle on each connected component of each pairwise overlap, verifies the cocycle condition on triple overlaps, and tests whether the cocycle is a coboundary.
The training loss for each chart combines three terms:
An optional cocycle loss on triple overlaps can be added but is not required — cocycle consistency emerges from reconstruction alone.
| Manifold | Orientable | Ambient |
Cover type | Key challenge | |
|---|---|---|---|---|---|
| ✓ | 2 | 3 | Tetrahedral (4 charts) | Good cover with contractible intersections | |
| Möbius band | ✗ | 2 | 3 | 2-chart y-split | Overlap has 2 components with opposite signs |
| Klein bottle | ✗ | 2 | 4 | Geodesic landmark | Cannot embed in |
|
|
✗ | 2 | 100 | Geodesic landmark | High-dimensional image data; non-embeddable in |
The quality of the open cover is often the limiting factor. The repository provides:
-
sphere_good_cover.py: Tetrahedral good cover for$S^2$ based on inscribed tetrahedron vertices, with stereographic projection for each chart. All intersections are contractible by construction. -
create_cover_from_neighborhoods(inatlasautoencoder.py): General-purpose cover from random landmark points with geodesic balls. -
Geodesic landmark covers (in Klein bottle / ℝP² scripts): Uses DREiMaC's
GeometryUtilsfor approximate geodesic distances.
A trial is considered converged when the following conditions hold simultaneously:
-
$\delta > 0$ : The non-degeneracy gap is positive, meaning all transition map Jacobians are non-singular. This ensures the sign cocycle is well-defined. -
Sign consistency: On each connected component of each overlap
$U_i \cap U_j$ , all sampled determinant signs agree (either all$+1$ or all$-1$ ). -
Cocycle condition: On triple overlaps,
$\omega_{ki} = \omega_{kj} \cdot \omega_{ji}$ holds.
When these conditions fail, the trial has not converged to a valid atlas and its orientability verdict should be discarded. The experiment scripts report both raw accuracy (all trials) and converged accuracy (trials meeting the above criteria).
If you use this code, please cite:
@misc{paluzohidalgo2026learningtangentbundlescharacteristic,
title={Learning Tangent Bundles and Characteristic Classes with Autoencoder Atlases},
author={Eduardo Paluzo-Hidalgo and Yuichi Ike},
year={2026},
eprint={2602.22873},
archivePrefix={arXiv},
primaryClass={math.AT},
url={https://arxiv.org/abs/2602.22873},
}Claude was used extensively for code and repository documentation.