Skip to main content

Gridworld environments and benchmarks for topological exploration in reinforcement learning

Project description

TopoGym

CI License Python

Gridworld environments and benchmarks for topological exploration in reinforcement learning, built on the Gymnasium API.

Every environment is generated on a real base manifold — a torus, a Möbius band, a Klein bottle, RP², a sphere, a 3-torus — and carved with holes, hidden chambers, and decoys that control its homology exactly. Every environment ships with certified topology metadata: its Betti numbers are computed from the actual free-space cubical complex at generation time, not assumed.


square-holes

square-rooms

annulus

plane-6holes

cylinder-rooms

mobius-rooms

torus-holes

torus-rooms

klein-rooms

rp2-rooms

sphere-holes

sphere-rooms

square-decoyfield

torus-goal-in-chamber

control-maze

control-zigzag

The 2d_bench_grid_small benchmark, rendered in reveal mode: walls (gray), holes (black), hidden doors (purple — agents see walls until they bump through), decoys (dark red — identical to chambers from outside, nothing inside), start (blue), goal (green). The full gallery, including the 3D and directed suites, is in docs/envs/.

Why

Exploration methods increasingly claim to exploit the shape of an environment — loops that shouldn't be re-searched, enclosed regions that must be entered to be known, irreversible passages that deserve caution. Testing those claims needs environments whose topology is known, varied, and controllable:

  • Known: every env carries certified Betti numbers (over ℤ/2, with integral homology and torsion where the math allows certification), Euler characteristic, orientability, genus, and a canonical description of any directional asymmetry.
  • Varied: seven 2D base manifolds and four 3D ones, from a plain square to RP², all built from one gluing-spec abstraction you can extend.
  • Controllable: a seeded generator that hits target Betti numbers and places chambers, decoys, hidden doors, one-way doors, and trapdoors — same config + same seed = byte-identical environment.

And because "my agent understands topology" must beat "my agent is good at generic novelty-seeking", every benchmark includes size-matched control environments that are hard to explore but topologically trivial.

Install

# directly from GitHub (no clone needed):
pip install git+https://github.com/jcarlson212/TopoGym.git

# or for development:
git clone https://github.com/jcarlson212/TopoGym.git && cd TopoGym
pip install -e ".[testing]"

pip install topogym from PyPI lands with the first tagged release. Dependencies: gymnasium, numpy. Nothing else.

Quick start

import gymnasium as gym
import topogym  # registers the TopoGym/* env ids

# A torus with 2 holes, a hidden chamber, and a decoy — fixed layout.
env = gym.make("TopoGym/Grid2D-v0", base="torus", size=17,
               n_holes=2, n_chambers=1, n_decoys=1, layout_seed=7)
obs, info = env.reset(seed=0)

print(info["topology"]["betti_z2"])     # [1, 5, 0] — certified
print(info["topology"]["genus"])        # 1
print(info["topology"]["homology"])     # {'H0': 'Z', 'H1': 'Z^5', 'H2': '0'}

obs, reward, terminated, truncated, info = env.step(2)  # forward
print(env.unwrapped.visited_betti())    # topology of the region seen so far

Or run a frozen benchmark:

import topogym.benchmarks as bench

for entry in bench.get_benchmark("2d_bench_grid_small"):
    env = entry.make()
    obs, info = env.reset(seed=0)
    print(f"{entry.name:24s} b={info['topology']['betti_z2']}")

rows = bench.benchmark_metadata("2d_bench_grid_small")  # dicts, pandas-ready

More in examples/.

Environments

TopoGym/Grid2D-v0 — an egocentric agent on a 2D base manifold. Actions Discrete(3): turn left / turn right / forward. The agent carries a local frame that is parallel-transported as it moves: crossing a Möbius/Klein/RP² seam mirrors its view (non-orientability is experienced, not just declared), and walking a loop around a cube-sphere corner rotates it by 90° (curvature). Observations are egocentric (2r+1, 2r+1) patches, occluded by walls — chamber interiors must be discovered, and decoys are visually identical to chambers until you try every wall.

TopoGym/Grid3D-v0 — a free agent in a 3D base manifold. Actions Discrete(6): moves along ±x/±y/±z. Observations are centered, occluded (2r+1)³ patches.

Both support obs_mode="global" (full grid + agent channel, for debugging or fully-observable baselines), reward_mode="goal" (MiniGrid-style success reward) or "none" (reward-free exploration), procedural layouts (a new layout every reset) or pinned ones (layout_seed=...).

Doors and asymmetric traversability

mechanic observed as behavior
hidden door (bump) wall, until opened opens permanently after tries bumps — persistence is rewarded
one-way door valve (visible) enterable from exactly one side, forever
trapdoor trapdoor (visible) passable once; seals the moment you step off it

Room features built from these: chambers (hidden interiors behind bump doors), decoys (chamber look-alikes with nothing inside), trap rooms (one-way in — an absorbing region), airlocks (one-way in, one-way out — a directed circuit that stays strongly connected), and trapdoor rooms (the way in seals; a hidden escape hatch leads out).

Door state never changes the free space's homology — a door cell is a free cell either way. Doors gate coverage and reversibility, and that is exactly what the metadata separates: Betti numbers describe the undirected shape; the asymmetry block describes the directed dynamics.

Bridges and the observed region

Partitions divide the world with narrow passages: dumbbells (one gap), passage pairs (two gaps close a loop: b₁ + 1), hidden bridges (bump-door gaps), on any base — a meridian wall on a torus makes closing the loop require the wraparound. Partitions come in two materials: wall (opaque) and moat (a pit — blocks movement but not sight, so the far side is visible before it is reachable).

Bridges are not a separate kind of topology. During exploration, every passage discovery is exactly one of: frontier growth (far side unknown), an H₀ merge (two known regions join), or an H₁ birth (a loop closure between already-connected regions). The envs track the observed region — everything seen and believed free — as a monotone filtration: info["known_components"] and info["h0_merges"] are maintained incrementally, and env.observed_betti() gives the full picture (its b₁ jumps are loop closures). Hidden doors participate naturally: a closed bump-door is believed to be a wall, so opening it is the discovery event.

What the metadata certifies instead is difficulty: the connectivity block reports graph bridges, articulation points, biconnected components, and max_bridge_split (the largest "smaller side" any single bridge separates) of the free-cell graph — how rare and late the homological events will be under naive exploration.

Base manifolds

Every 2D base except the sphere is one rectangular fundamental domain plus a gluing rule per axis — that is the abstraction to extend if you want new surfaces:

base gluing (x, y) χ orientable genus b(ℤ/2) H₁ torsion
square wall, wall 1 yes 0 (1, 0, 0)
cylinder wrap, wall 0 yes 0 (1, 1, 0)
torus wrap, wrap 0 yes 1 (1, 2, 1)
mobius flip, wall 0 no ✝1 (1, 1, 0)
klein flip, wrap 0 no ✝2 (1, 2, 1) ℤ/2
rp2 flip, flip 1 no ✝1 (1, 1, 1) ℤ/2
sphere cube surface 2 yes 0 (1, 0, 1)

✝ = demigenus (crosscap number). Note RP² and the Klein bottle: their interesting H₁ is torsion, invisible to rational Betti numbers — which is why TopoGym certifies homology over ℤ/2 and reports torsion explicitly.

3D bases: box (trivial), solid_torus (wrap one axis, b₁=1 — the 3D annulus), torus3 (wrap all axes, b₁=3), plus presets annulus, x_holes (2D) and shell (3D, b₂=1) built from large intrinsic holes.

The generator

from topogym.generation import TopoGenConfig2D, generate_2d

cfg = TopoGenConfig2D(
    base="klein", size=19,
    n_holes=2, hole_shapes=("rect", "disc", "blob", "plus"), hole_size=(2, 4),
    n_chambers=2, n_decoys=1, door_tries=(2, 5),
    n_trap_rooms=0, n_airlocks=1,            # directed features
    # target_b1=7,                           # or: solve n_holes for me
)
layout = generate_2d(cfg, seed=42)           # deterministic
print(layout.metadata.betti_z2)              # computed AND verified

Placement works by parallel transport, so shapes wrap seams and cross cube-sphere edges correctly; margins keep every obstacle's homology contribution independent; the generator retries until the computed homology matches the analytic expectation, then certifies it. Each feature's contribution:

feature 2D 3D
hole / blob +1 b₁ +1 b₂
ring +1 b₁, +1 b₂
chamber / decoy / trap room +1 b₁ +1 b₂
airlock / trapdoor room +2 b₁ +1 b₁, +1 b₂
partition, K gaps (attached) +(K−1) b₁ +(K−1) b₁
partition, K gaps (ring/belt) +K b₁

Certified metadata

env.unwrapped.topology (also in info["topology"] at reset, as a plain dict) — designed to be swept over programmatically:

{
  "dim": 2, "base_map": "torus", "size": [17, 17], "style": "rooms",
  "layout_seed": 7, "n_holes": 2, "n_chambers": 1, "n_decoys": 1,
  "door_tries": [3], "n_cells": 289, "n_free_cells": 243,
  "betti_z2": [1, 5, 0],            # certified: computed from the complex
  "euler_characteristic": -4,
  "orientable": true, "genus": 1, "demigenus": null,
  "n_boundary_components": 4,
  "betti_q": [1, 5, 0], "h1_torsion": [], "homology": {"H0": "Z", "H1": "Z^5", "H2": "0"},
  "asymmetry": {
    "is_symmetric": true, "mechanisms": [], "n_sccs": 1,
    "largest_scc_frac": 1.0, "n_absorbing_sccs": 0,
    "goal_in_start_scc": true, "n_consumable_transitions": 0,
    "feature_counts": {"trap_room": 0, "airlock": 0, "trapdoor_room": 0}
  },
  "connectivity": {
    "n_bridges": 0, "n_articulation_points": 0,
    "n_biconnected_components": 1, "max_bridge_split": 0
  },
  "n_partitions": 0,
  "certified": {"betti_z2": true, "betti_q": true, "h1_torsion": true,
                "asymmetry": true, "connectivity": true, "genus": true},
  "base": {"name": "torus", "orientable": true, "genus": 1, "...": "..."}
}

Certification levels: betti_z2 and asymmetry (SCC condensation of the actual directed transition graph) are always computed. betti_q, torsion, genus/orientability are certified for all 2D environments (surface classification) and obstacle-free 3D bases; 3D-with-obstacles reports betti_q_expected instead and says so in certified.

Benchmarks

collection envs contents
2d_bench_grid_small 16 all 7 bases, holes/chambers/decoys, goal-in-chamber, 2 controls
3d_bench_grid_small 8 rings (b₁), voids (b₂), rooms, solid torus, 3-torus, shell, control
2d_bench_grid_small_directed 8 trap rooms, airlocks, trapdoor rooms on 4 bases
3d_bench_grid_small_directed 4 the same mechanics in 3D
2d_bench_grid_small_bridges 8 dumbbells, passage pairs, moats, hidden bridges, torus meridian, sphere belt
3d_bench_grid_small_bridges 4 tunnel dumbbells, tunnel pairs, hidden tunnels, solid-torus gate

Each entry pins (config, layout_seed) — everyone runs byte-identical environments — and is registered as a Gymnasium id (TopoGym/Bench2DSmall-torus-rooms-v0, ...). Browse them all in the gallery.

Suggested protocol (we follow it in examples/): fix ≥5 agent seeds per environment; report mean ± std; use reward-free mode with coverage / visited_betti() curves for exploration claims (time-to-discover each homology class is the interesting curve); report absorbing-region outcomes in directed envs explicitly (an agent that dies in a trap room is a data point, not a dropped run); always include the controls.

Contributing 🤝

Contributions are very welcome — this project is designed to be built on:

  • Add an environment (no code needed): design it with scripts/new_env.py, iterate on seeds, submit the frozen entry. Full walkthrough: docs/contributing_environments.md.
  • Add a base manifold, hole shape, or door mechanic: the extension points are documented in the same guide; if the generator isn't general enough for your idea, generalize it and send that too.
  • Add benchmarks: propose a collection (bigger sizes, new mechanic mixes) via a new-environment issue.

See CONTRIBUTING.md for dev setup and the PR checklist. All new topology must come with certified tests — the homology engine is the referee.

Roadmap

  • Genus-g surfaces (polygon gluings beyond rectangles)
  • Non-orientable 3D bases (frame transport in 3D)
  • Persistent-homology observers / wrappers for visited-region analysis
  • Vectorized generation, larger benchmark tiers (*_medium, *_large)
  • Community benchmark collection

Citation

If you use TopoGym in your research, please cite it (see also CITATION.cff):

@software{carlson2026topogym,
  author  = {Carlson, Jason},
  title   = {TopoGym: Environments and Benchmarks for Topological
             Exploration in Reinforcement Learning},
  year    = {2026},
  url     = {https://github.com/jcarlson212/TopoGym},
  version = {0.1.0}
}

License

Apache 2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

topogym-0.1.0.tar.gz (66.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

topogym-0.1.0-py3-none-any.whl (58.6 kB view details)

Uploaded Python 3

File details

Details for the file topogym-0.1.0.tar.gz.

File metadata

  • Download URL: topogym-0.1.0.tar.gz
  • Upload date:
  • Size: 66.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for topogym-0.1.0.tar.gz
Algorithm Hash digest
SHA256 76d4ee7360708e5ee5a427fb159523c6b73cde956c9d0749c72f771c551e9d82
MD5 4575863e6ad1213faff40cadd6d6efae
BLAKE2b-256 f16fdd9528358b58f9b23983042b9d122a003666a45a2569e3d81c1a31444662

See more details on using hashes here.

Provenance

The following attestation bundles were made for topogym-0.1.0.tar.gz:

Publisher: release.yml on jcarlson212/TopoGym

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file topogym-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: topogym-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 58.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for topogym-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 499296feda49de2b1d792318314037c1c442afcf7e2365850e4e2c0d6edb38bb
MD5 89b28183cf454bd5687bcf66d40d296e
BLAKE2b-256 d35fbf9d136bf9b237963c7dbe416d82900269d3c31e0c20849f1bd0929d2bec

See more details on using hashes here.

Provenance

The following attestation bundles were made for topogym-0.1.0-py3-none-any.whl:

Publisher: release.yml on jcarlson212/TopoGym

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page