Skip to content

Benchmark

Time of the main operations at several sizes. The script benchmarks/bench.py produces the numbers; it uses no random numbers, so every run does the same work, and each time is the best of three runs.

uv run python benchmarks/bench.py --markdown                # the tables of this page
uv run python benchmarks/bench.py --markdown --tactile      # adds the comparison with tactile
uv run python benchmarks/bench.py --sizes 1000 --repeat 1   # a quick run, plain text

The tables were measured with Python 3.13, NumPy 2.5 and SciPy 1.18 on one machine (AMD Ryzen 7 5825U, Linux under WSL2; the code is single-threaded), with other programs running. Absolute times differ between machines; the ratios and the growth with the number of tiles are what the page is for. The benchmark is not part of the default CI.

Operations

The cases are the types IH7 (hexagon, 3 aspects), IH46 (quadrilateral) and IH84 (triangle) at their default parameters and the preset puzzle_piece (IH41 with curved edges). The target tile counts are 1,000, 10,000 and 100,000; the column Tiles is the number of tiles returned, which also includes the tiles on the boundary of the region.

  • fill_region: IsohedralTiling.fill_region((0, 0, 1, 1), n_tiles=N), which sets the frame and selects the tiles that overlap the region.
  • vertices: TileSet.vertices(), the (n, n_vertices, 2) array of corners.
  • adjacency: TileSet.adjacency("edge"), the sparse matrix from the lattice table.
  • neighbor_lists: TileSet.neighbor_lists("edge"), the sorted neighbors of every tile.
  • geometric_adjacency: the method for arbitrary polygons, on the vertex arrays of the same tiles. The benchmark checks that its edge matrix equals the lattice matrix.
Case Tiles fill_region vertices adjacency neighbor_lists geometric_adjacency
IH7 1,068 13.1 ms 475 µs 695 µs 1.2 ms 3.3 ms
IH7 10,300 119.0 ms 5.0 ms 2.5 ms 9.2 ms 31.8 ms
IH7 100,764 1.21 s 47.2 ms 23.2 ms 84.9 ms 348.5 ms
IH46 1,024 6.6 ms 315 µs 432 µs 1.0 ms 2.7 ms
IH46 10,000 67.5 ms 3.4 ms 1.7 ms 7.8 ms 23.5 ms
IH46 101,124 796.8 ms 31.8 ms 16.4 ms 77.0 ms 262.4 ms
IH84 1,118 5.9 ms 264 µs 331 µs 1.1 ms 2.7 ms
IH84 10,108 50.8 ms 2.5 ms 1.4 ms 8.0 ms 21.5 ms
IH84 101,398 586.1 ms 23.8 ms 12.8 ms 72.7 ms 229.7 ms
puzzle_piece 1,024 84.6 ms 314 µs 294 µs 908 µs 2.6 ms
puzzle_piece 10,400 610.3 ms 3.2 ms 1.4 ms 7.3 ms 23.2 ms
puzzle_piece 101,124 2.14 s 31.9 ms 15.5 ms 75.2 ms 252.1 ms

All times grow about linearly with the number of tiles. The lattice adjacency is 5 to 17 times faster than geometric_adjacency for the same tiles, and it does not depend on a tolerance. fill_region of a tiling with curves is slower than for straight tilings because the tiles are selected by the overlap of their curved polygons (outline="curved"); outline="straight" gives the time of the straight tilings.

Validity test

check_prototile for the default parameters of the types above and, for puzzle_piece, with its curves (the mean of 100 calls). The cost does not depend on the number of tiles.

Case check_prototile
IH7 155 µs
IH46 154 µs
IH84 147 µs
puzzle_piece 5.1 ms

Comparison with tactile

tactile has no adjacency and no region selection, so only the scanline fill is compared: tactile.IsohedralTiling.fill_region_bounds (converted to a list) against IsohedralTiling.fill, on a square region in native coordinates that holds about N tiles. The two return the same tiles; the count column shows it. Sizes above 10,000 tiles are not run with tactile.

Type Tiles (tactile) tactile fill Tiles (isotile) isotile fill Speedup
IH7 1,083 2.7 ms 1,083 368 µs 7x
IH7 10,398 27.3 ms 10,398 1.4 ms 20x
IH46 1,122 3.1 ms 1,122 346 µs 9x
IH46 10,404 28.0 ms 10,404 1.9 ms 15x
IH84 1,144 3.1 ms 1,144 385 µs 8x
IH84 10,242 25.1 ms 10,242 1.9 ms 13x

The speedup grows with the number of tiles because the fill of isotile runs in NumPy and tactile loops in Python.