topogym 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
topogym/__init__.py ADDED
@@ -0,0 +1,31 @@
1
+ """TopoGym: environments and benchmarks for topological exploration in RL.
2
+
3
+ Quick start::
4
+
5
+ import gymnasium as gym
6
+ import topogym # registers the TopoGym/* env ids
7
+
8
+ env = gym.make("TopoGym/Grid2D-v0", base="torus", n_holes=3, layout_seed=7)
9
+ obs, info = env.reset(seed=0)
10
+ print(info["topology"]["betti_z2"]) # certified: [1, 6, 0]
11
+ """
12
+
13
+ from gymnasium.envs.registration import register
14
+
15
+ from topogym.core.metadata import TopologyMetadata
16
+ from topogym.generation import TopoGenConfig2D, TopoGenConfig3D
17
+
18
+ __version__ = "0.1.0"
19
+ __all__ = ["TopologyMetadata", "TopoGenConfig2D", "TopoGenConfig3D"]
20
+
21
+ register(
22
+ id="TopoGym/Grid2D-v0",
23
+ entry_point="topogym.envs:TopoGrid2DEnv",
24
+ )
25
+ register(
26
+ id="TopoGym/Grid3D-v0",
27
+ entry_point="topogym.envs:TopoGrid3DEnv",
28
+ )
29
+
30
+ # Benchmark entries register their pinned env ids on import.
31
+ from topogym import benchmarks as _benchmarks # noqa: E402,F401
@@ -0,0 +1,300 @@
1
+ """Benchmark collections: frozen (config, layout_seed) suites.
2
+
3
+ Each entry pins a generator config and a layout seed, so every user runs
4
+ byte-identical environments. Entries are also registered as Gymnasium ids
5
+ (``TopoGym/<Collection>-<name>-v0``).
6
+
7
+ Collections
8
+ -----------
9
+ - ``2d_bench_grid_small``: 16 environments covering every 2D base manifold,
10
+ chambers/decoys, and two topologically-trivial controls.
11
+ - ``3d_bench_grid_small``: 8 environments covering the 3D bases, rings
12
+ (b1), voids (b2), rooms, and a 3D maze control.
13
+ - ``2d_bench_grid_small_directed`` / ``3d_bench_grid_small_directed``:
14
+ mirrored suites whose environments contain one-way doors and trapdoors
15
+ (trap rooms, airlocks, trapdoor rooms) — asymmetric traversability, see
16
+ the ``asymmetry`` metadata block.
17
+
18
+ Usage::
19
+
20
+ import topogym.benchmarks as bench
21
+
22
+ for entry in bench.get_benchmark("2d_bench_grid_small"):
23
+ env = entry.make()
24
+ obs, info = env.reset(seed=0)
25
+ print(entry.name, info["topology"]["betti_z2"])
26
+
27
+ rows = bench.benchmark_metadata("2d_bench_grid_small") # list of dicts
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ from dataclasses import dataclass
33
+
34
+ import gymnasium as gym
35
+ from gymnasium.envs.registration import register
36
+
37
+ from topogym.generation import TopoGenConfig2D, TopoGenConfig3D
38
+
39
+ __all__ = [
40
+ "BenchmarkEntry",
41
+ "BENCHMARKS",
42
+ "benchmark_names",
43
+ "get_benchmark",
44
+ "benchmark_metadata",
45
+ ]
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class BenchmarkEntry:
50
+ name: str
51
+ collection: str
52
+ env_id: str
53
+ dim: int
54
+ config: object # TopoGenConfig2D | TopoGenConfig3D
55
+ layout_seed: int
56
+ description: str
57
+
58
+ def make(self, **kwargs) -> gym.Env:
59
+ return gym.make(self.env_id, **kwargs)
60
+
61
+ def metadata(self):
62
+ """Generate (or re-generate) the layout and return its certified
63
+ :class:`TopologyMetadata`."""
64
+ from topogym.generation import generate_2d, generate_3d
65
+
66
+ gen = generate_2d if self.dim == 2 else generate_3d
67
+ return gen(self.config, self.layout_seed).metadata
68
+
69
+
70
+ def _c2(**kw) -> TopoGenConfig2D:
71
+ return TopoGenConfig2D(**kw)
72
+
73
+
74
+ def _c3(**kw) -> TopoGenConfig3D:
75
+ return TopoGenConfig3D(**kw)
76
+
77
+
78
+ _RAW = {
79
+ "2d_bench_grid_small": [
80
+ ("square-holes", 101, "3 holes on a disc: b1 = 3, nothing hidden",
81
+ _c2(base="square", size=15, n_holes=3, n_chambers=0, n_decoys=0)),
82
+ ("square-rooms", 102, "holes + hidden chambers + a decoy",
83
+ _c2(base="square", size=17, n_holes=2, n_chambers=2, n_decoys=1)),
84
+ ("square-decoyfield", 103,
85
+ "many identical-looking rooms; most are empty decoys",
86
+ _c2(base="square", size=21, n_holes=0, n_chambers=2, n_decoys=3,
87
+ door_tries=(3, 6))),
88
+ ("annulus", 104, "thick annulus base + one chamber and one decoy",
89
+ _c2(base="annulus", size=19, n_holes=0, n_chambers=1, n_decoys=1)),
90
+ ("plane-6holes", 105, "plane with 6 large holes: b1 = 6",
91
+ _c2(base="x_holes", size=21, n_base_holes=6, n_holes=0,
92
+ n_chambers=0, n_decoys=0)),
93
+ ("cylinder-rooms", 106, "cylinder: one loop is the world itself",
94
+ _c2(base="cylinder", size=17, n_holes=1, n_chambers=1, n_decoys=1)),
95
+ ("mobius-rooms", 107, "Mobius band: crossing the seam mirrors you",
96
+ _c2(base="mobius", size=17, n_holes=1, n_chambers=1, n_decoys=1)),
97
+ ("torus-holes", 108, "torus with 2 holes: b1 = 3",
98
+ _c2(base="torus", size=15, n_holes=2, n_chambers=0, n_decoys=0)),
99
+ ("torus-rooms", 109, "torus with chambers and a decoy",
100
+ _c2(base="torus", size=17, n_holes=1, n_chambers=2, n_decoys=1)),
101
+ ("torus-goal-in-chamber", 110,
102
+ "the goal hides inside a chamber: doors must be found",
103
+ _c2(base="torus", size=17, n_holes=1, n_chambers=2, n_decoys=1,
104
+ goal_in_chamber=True, door_tries=(2, 4))),
105
+ ("klein-rooms", 111, "Klein bottle: torus-like but non-orientable",
106
+ _c2(base="klein", size=17, n_holes=1, n_chambers=1, n_decoys=1)),
107
+ ("rp2-rooms", 112, "RP^2: the antipodal world",
108
+ _c2(base="rp2", size=17, n_holes=1, n_chambers=1, n_decoys=1)),
109
+ ("sphere-holes", 113, "cube-sphere with 3 holes",
110
+ _c2(base="sphere", size=7, n_holes=3, n_chambers=0, n_decoys=0)),
111
+ ("sphere-rooms", 114, "cube-sphere with chambers and a decoy",
112
+ _c2(base="sphere", size=7, n_holes=1, n_chambers=1, n_decoys=1)),
113
+ ("control-maze", 115,
114
+ "control: perfect maze, hard to explore, b1 = 0",
115
+ _c2(base="square", size=17, style="maze")),
116
+ ("control-zigzag", 116,
117
+ "control: serpentine corridor, one long path, b1 = 0",
118
+ _c2(base="square", size=17, style="zigzag")),
119
+ ],
120
+ "2d_bench_grid_small_directed": [
121
+ ("square-traproom", 201, "a one-way room: enter and you stay",
122
+ _c2(base="square", size=19, n_holes=1, n_chambers=0, n_decoys=0,
123
+ n_trap_rooms=1)),
124
+ ("square-airlock", 202, "a directed circuit: in one door, out the other",
125
+ _c2(base="square", size=19, n_holes=1, n_chambers=0, n_decoys=0,
126
+ n_airlocks=1)),
127
+ ("square-trapdoor", 203,
128
+ "a trapdoor room: the way in seals, a hidden hatch leads out",
129
+ _c2(base="square", size=19, n_holes=1, n_chambers=0, n_decoys=0,
130
+ n_trapdoor_rooms=1, trapdoor_escape_tries=(3, 5))),
131
+ ("cylinder-trapdoor", 204, "trapdoor room on a cylinder",
132
+ _c2(base="cylinder", size=19, n_holes=1, n_chambers=0, n_decoys=0,
133
+ n_trapdoor_rooms=1)),
134
+ ("torus-traproom", 205, "trap room on a torus",
135
+ _c2(base="torus", size=19, n_holes=1, n_chambers=1, n_decoys=0,
136
+ n_trap_rooms=1)),
137
+ ("torus-airlock-mix", 206, "airlock + trapdoor room on a torus",
138
+ _c2(base="torus", size=21, n_holes=1, n_chambers=0, n_decoys=1,
139
+ n_airlocks=1, n_trapdoor_rooms=1)),
140
+ ("sphere-traproom", 207, "trap room on the cube-sphere",
141
+ _c2(base="sphere", size=7, n_holes=1, n_chambers=0, n_decoys=0,
142
+ n_trap_rooms=1)),
143
+ ("square-gauntlet", 208,
144
+ "one of everything: trap room, airlock, trapdoor room, decoy",
145
+ _c2(base="square", size=25, n_holes=1, n_chambers=1, n_decoys=1,
146
+ n_trap_rooms=1, n_airlocks=1, n_trapdoor_rooms=1)),
147
+ ],
148
+ "3d_bench_grid_small": [
149
+ ("box-blobs", 301, "2 solid obstacles: b2 = 2 enclosing shells",
150
+ _c3(base="box", size=10, n_rings=0, n_blobs=2, n_chambers=0,
151
+ n_decoys=0)),
152
+ ("box-ring", 302, "a ring obstacle: b1 = 1, b2 = 1",
153
+ _c3(base="box", size=10, n_rings=1, n_blobs=0, n_chambers=0,
154
+ n_decoys=0)),
155
+ ("box-rooms", 303, "a hollow chamber and a sealed decoy",
156
+ _c3(base="box", size=12, n_rings=0, n_blobs=0, n_chambers=1,
157
+ n_decoys=1)),
158
+ ("box-mixed", 304, "ring + void + chamber",
159
+ _c3(base="box", size=12, n_rings=1, n_blobs=1, n_chambers=1,
160
+ n_decoys=0)),
161
+ ("solid-torus", 305, "solid torus: one loop is the world itself",
162
+ _c3(base="solid_torus", size=10, n_rings=1, n_blobs=1,
163
+ n_chambers=0, n_decoys=0)),
164
+ ("torus3", 306, "3-torus: wraps in every direction, b1 = 3",
165
+ _c3(base="torus3", size=8, n_rings=0, n_blobs=1, n_chambers=0,
166
+ n_decoys=0)),
167
+ ("shell", 307, "spherical shell: a big void you can never enter",
168
+ _c3(base="shell", size=10, n_rings=0, n_blobs=0, n_chambers=0,
169
+ n_decoys=0)),
170
+ ("control-maze3d", 308, "control: perfect 3D maze, b1 = b2 = 0",
171
+ _c3(base="box", size=9, style="maze")),
172
+ ],
173
+ "2d_bench_grid_small_bridges": [
174
+ ("square-dumbbell", 501,
175
+ "two rooms, one narrow passage: b1 = 0, all bottleneck",
176
+ _c2(base="square", size=17, n_holes=0, n_chambers=0, n_decoys=0,
177
+ n_partitions=1, partition_gaps=(1, 1),
178
+ partition_hidden_gaps=(0, 0))),
179
+ ("square-twin-passages", 502,
180
+ "two passages through one wall: the loop closure env",
181
+ _c2(base="square", size=17, n_holes=0, n_chambers=0, n_decoys=0,
182
+ n_partitions=1, partition_gaps=(2, 2),
183
+ partition_hidden_gaps=(0, 0))),
184
+ ("square-moat-hidden", 503,
185
+ "a moat you can see across; one open bridge, one hidden",
186
+ _c2(base="square", size=19, n_holes=0, n_chambers=0, n_decoys=0,
187
+ n_partitions=1, partition_gaps=(2, 2),
188
+ partition_hidden_gaps=(1, 1), partition_material="moat",
189
+ door_tries=(2, 4))),
190
+ ("square-triple-rooms", 504, "two dividing walls, three regions",
191
+ _c2(base="square", size=21, n_holes=0, n_chambers=0, n_decoys=0,
192
+ n_partitions=2, partition_gaps=(1, 2),
193
+ partition_hidden_gaps=(0, 1))),
194
+ ("cylinder-ring-gate", 505, "a gate across the cylinder",
195
+ _c2(base="cylinder", size=17, n_holes=0, n_chambers=0, n_decoys=0,
196
+ n_partitions=1, partition_gaps=(1, 1),
197
+ partition_hidden_gaps=(0, 0))),
198
+ ("torus-meridian", 506,
199
+ "a meridian wall on the torus: close the loop through the wrap",
200
+ _c2(base="torus", size=17, n_holes=1, n_chambers=0, n_decoys=0,
201
+ n_partitions=1, partition_gaps=(1, 1),
202
+ partition_hidden_gaps=(0, 0))),
203
+ ("sphere-belt", 507, "an equatorial belt with two passages",
204
+ _c2(base="sphere", size=7, n_holes=0, n_chambers=0, n_decoys=0,
205
+ n_partitions=1, partition_gaps=(2, 2),
206
+ partition_hidden_gaps=(0, 0))),
207
+ ("square-bridge-gauntlet", 508,
208
+ "a moat with a hidden bridge, plus a chamber and a decoy",
209
+ _c2(base="square", size=21, n_holes=0, n_chambers=1, n_decoys=1,
210
+ n_partitions=1, partition_gaps=(2, 2),
211
+ partition_hidden_gaps=(1, 1), partition_material="moat")),
212
+ ],
213
+ "3d_bench_grid_small_bridges": [
214
+ ("box-dumbbell", 601, "two chambers of space, one tunnel",
215
+ _c3(base="box", size=11, n_rings=0, n_blobs=0, n_chambers=0,
216
+ n_decoys=0, n_partitions=1, partition_gaps=(1, 1),
217
+ partition_hidden_gaps=(0, 0))),
218
+ ("box-two-tunnels", 602, "two tunnels through one wall: b1 = 1",
219
+ _c3(base="box", size=11, n_rings=0, n_blobs=0, n_chambers=0,
220
+ n_decoys=0, n_partitions=1, partition_gaps=(2, 2),
221
+ partition_hidden_gaps=(0, 0))),
222
+ ("box-hidden-tunnel", 603, "one tunnel open, one hidden",
223
+ _c3(base="box", size=11, n_rings=0, n_blobs=0, n_chambers=0,
224
+ n_decoys=0, n_partitions=1, partition_gaps=(2, 2),
225
+ partition_hidden_gaps=(1, 1), door_tries=(2, 4))),
226
+ ("solid-torus-gate", 604,
227
+ "a gate disc across the solid torus: the wrap loop passes it",
228
+ _c3(base="solid_torus", size=11, n_rings=0, n_blobs=1,
229
+ n_chambers=0, n_decoys=0, n_partitions=1,
230
+ partition_gaps=(1, 1), partition_hidden_gaps=(0, 0))),
231
+ ],
232
+ "3d_bench_grid_small_directed": [
233
+ ("box-traproom", 401, "a one-way room in 3D",
234
+ _c3(base="box", size=12, n_rings=0, n_blobs=1, n_chambers=0,
235
+ n_decoys=0, n_trap_rooms=1)),
236
+ ("box-airlock", 402, "a 3D airlock: directed circuit through a room",
237
+ _c3(base="box", size=12, n_rings=0, n_blobs=0, n_chambers=0,
238
+ n_decoys=0, n_airlocks=1)),
239
+ ("box-trapdoor", 403, "3D trapdoor room with a hidden escape hatch",
240
+ _c3(base="box", size=12, n_rings=0, n_blobs=0, n_chambers=0,
241
+ n_decoys=0, n_trapdoor_rooms=1)),
242
+ ("solid-torus-traproom", 404, "trap room in a solid torus",
243
+ _c3(base="solid_torus", size=12, n_rings=0, n_blobs=0,
244
+ n_chambers=0, n_decoys=0, n_trap_rooms=1)),
245
+ ],
246
+ }
247
+
248
+ _COLLECTION_ID = {
249
+ "2d_bench_grid_small": "Bench2DSmall",
250
+ "2d_bench_grid_small_directed": "Bench2DSmallDirected",
251
+ "2d_bench_grid_small_bridges": "Bench2DSmallBridges",
252
+ "3d_bench_grid_small": "Bench3DSmall",
253
+ "3d_bench_grid_small_directed": "Bench3DSmallDirected",
254
+ "3d_bench_grid_small_bridges": "Bench3DSmallBridges",
255
+ }
256
+
257
+ BENCHMARKS = {}
258
+ for _coll, _entries in _RAW.items():
259
+ _out = []
260
+ for _name, _seed, _desc, _cfg in _entries:
261
+ _env_id = f"TopoGym/{_COLLECTION_ID[_coll]}-{_name}-v0"
262
+ _dim = 2 if isinstance(_cfg, TopoGenConfig2D) else 3
263
+ register(
264
+ id=_env_id,
265
+ entry_point=(
266
+ "topogym.envs:TopoGrid2DEnv" if _dim == 2
267
+ else "topogym.envs:TopoGrid3DEnv"
268
+ ),
269
+ kwargs={"config": _cfg, "layout_seed": _seed},
270
+ )
271
+ _out.append(BenchmarkEntry(
272
+ name=_name, collection=_coll, env_id=_env_id, dim=_dim,
273
+ config=_cfg, layout_seed=_seed, description=_desc,
274
+ ))
275
+ BENCHMARKS[_coll] = tuple(_out)
276
+
277
+
278
+ def benchmark_names():
279
+ return list(BENCHMARKS)
280
+
281
+
282
+ def get_benchmark(name: str):
283
+ if name not in BENCHMARKS:
284
+ raise KeyError(
285
+ f"unknown benchmark {name!r}; available: {benchmark_names()}"
286
+ )
287
+ return BENCHMARKS[name]
288
+
289
+
290
+ def benchmark_metadata(name: str):
291
+ """Certified metadata dicts for every entry — ready for sweeping
292
+ (each row includes the entry name and env id)."""
293
+ rows = []
294
+ for entry in get_benchmark(name):
295
+ row = entry.metadata().to_dict()
296
+ row["name"] = entry.name
297
+ row["env_id"] = entry.env_id
298
+ row["description"] = entry.description
299
+ rows.append(row)
300
+ return rows
@@ -0,0 +1,43 @@
1
+ """Core abstractions: base manifolds, cubical homology, metadata."""
2
+
3
+ from topogym.core import constants
4
+ from topogym.core.basemap import (
5
+ BASE_MAPS_2D,
6
+ BASE_MAPS_3D,
7
+ AgentState,
8
+ BaseMap2D,
9
+ BaseMapInfo,
10
+ Boundary,
11
+ CubeSphere2D,
12
+ RectGluing2D,
13
+ RectGluing3D,
14
+ make_base_map_2d,
15
+ make_base_map_3d,
16
+ )
17
+ from topogym.core.homology import (
18
+ Complex3DSummary,
19
+ Surface2DSummary,
20
+ analyze_2d,
21
+ analyze_3d,
22
+ gf2_rank,
23
+ )
24
+
25
+ __all__ = [
26
+ "constants",
27
+ "AgentState",
28
+ "BaseMap2D",
29
+ "BaseMapInfo",
30
+ "Boundary",
31
+ "CubeSphere2D",
32
+ "RectGluing2D",
33
+ "RectGluing3D",
34
+ "BASE_MAPS_2D",
35
+ "BASE_MAPS_3D",
36
+ "make_base_map_2d",
37
+ "make_base_map_3d",
38
+ "Surface2DSummary",
39
+ "Complex3DSummary",
40
+ "analyze_2d",
41
+ "analyze_3d",
42
+ "gf2_rank",
43
+ ]