GomJau-Hogg Notation¶
The GomJau-Hogg notation describes uniform Euclidean tilings as short codes
like 6-3-3/r60/r(h5). Each code is a sequence of stages separated by /:
- Polygon placement (stage 1): polygons separated by
-(sequential placement) and,(around the same vertex). A0means "skip an attachment slot". - Transforms (stage 2 and beyond): rotations (
r…) and mirrors (m…) that expand the seed configuration into a tiling.
See Gómez-Jáuregui & Hogg, Symmetry 13(12), 2021.
pleat turns a GJH code into a reusable tileset in two steps. First, build a finite patch: place the seed polygons of stage 1, then iteratively apply each transform stage's rotations and mirrors until no new tiles fit inside a bounding box. Then distill a TilesetSpec: partition the patch's faces by congruency and its edges by their local neighbourhood, iterating the two partitions until they stop refining each other. The fixpoint yields one exemplar face per equivalence class with labelled outgoing edges — a minimal repeating tileset that can be grown indefinitely via from_tiles.
This notebook covers:
- the
gjh()function and the cached library of 89 known tilings, - inspecting the intermediate
TilesetSpec, - compiling novel codes with the parser,
- filling a bounded region with
fill_domain.
from pleat import example_graphs
from pleat.rendering import multi_show
from pleat.gjh import gjh, gjh_spec, gjh_graph, GJH_CODES
Looking up a known tiling¶
gjh(code) returns a list of prototiles you can hand to from_tiles exactly
like the named factories in example_tilesets. Codes in the cached library
load instantly; novel codes are compiled on demand.
codes_to_show = ['3/m30/r(h2)', '6-3-6/m30/r(v4)', '12-3/m30/r(h3)']
Gs = [example_graphs.from_tiles(gjh(code), rings=3) for code in codes_to_show]
multi_show(Gs, titles=codes_to_show, face_inset=0.05, render_vertices=False, cell_size=3)
Browsing the cached library¶
GJH_CODES is the ordered list of all cached codes (regular → 1-uniform →
2-uniform → 3-uniform, matching the source paper).
print(f"Library size: {len(GJH_CODES)}")
print(f"First five: {GJH_CODES[:5]}")
print(f"Last three: {GJH_CODES[-3:]}")
Library size: 89 First five: ['3/m30/r(h2)', '6/m30/r(h1)', '4/m45/r(h1)', '12-3/m30/r(h3)', '6-4-3/m30/r(c2)'] Last three: ['4-4-3-3-3/m90/r(h9)/r(h3)', '4-4-3-3-3/m(h9)/r(h1)/r(v1)', '4-4-3-3-3/m(h9)/r(h1)/r(h3)']
# Show some random codes
import random
random.seed(0)
random_codes = random.sample(GJH_CODES, 12)
random_Gs = [example_graphs.from_tiles(gjh(code), rings=3) for code in random_codes]
multi_show(random_Gs, titles=random_codes, face_inset=0.05, render_vertices=False, render_edges=False, face_color_by=lambda f: f.attributes.get('label'), cell_size=3, ncols=3)
Inspecting the spec¶
gjh_spec(code) returns the declarative TilesetSpec underlying a tiling.
The keys are tile names; each value lists the
neighbour of every edge in cyclic order, where e.g. ('a', 1) means edge one of tile 'a'.
spec = gjh_spec('3-6/m30/r(c2)') # the 3.6.3.6 (trihexagonal) tiling
for name, edges in spec.items():
print(f" {name}: {edges}")
a: [('a', 2), ('b', 0), ('a', 0), ('a', 2), ('b', 0), ('a', 0)]
b: [('a', 1), ('b', 2), ('b', 1)]
Filling a bounded region¶
fill_domain grows a tileset until it covers a given Domain rather than a
fixed number of rings. Useful for rendering exact-size images.
tiles = gjh('6-3-3/r60/r(h5)') # 3.3.3.3.6
domain = example_graphs.RectangleDomain(12, 8)
G = example_graphs.fill_domain(tiles, domain)
# Render with a fixed scale and a surface sized to the domain — anything past
# the domain edge falls outside the SVG/PNG viewport and is not drawn.
scale = 30
a, b = domain.size
G.show(scale=scale, width=int(a * scale), height=int(b * scale), face_inset=0.05, render_vertices=False)
Colouring faces by tile role¶
The distillation step in gjh_spec assigns each face to one of N equivalence classes (a, b, c, …). For a single-uniform tiling these classes coincide with congruency (one class per polygon shape), but for 2- and 3-uniform tilings the distillation distinguishes triangles or squares that play different roles — same shape, different gluing pattern. The label is stored on each face as face['label'], so we can pass a small callable to face_color_by to colour by it.
Below: the same 3-uniform tiling rendered three ways — outlines only, coloured by congruency (shape), and coloured by label (role).
# A 3-uniform tiling: two square roles and three triangle roles.
code = '4-3,3-0,4,3/r/r(h2)/r(h18)'
G = example_graphs.from_tiles(gjh(code), rings=4)
multi_show(
[G, G, G],
titles=['outlines', 'by congruency', 'by label'],
render_faces=True, face_inset=0.05, render_vertices=False,
per_subplot_kwargs=[
dict(render_faces=False),
dict(face_color_by='congruency', render_edges=False),
dict(face_color_by=lambda f: f.attributes.get('label'), render_edges=False),
],
cell_size=3,
)
Compiling a novel code¶
For codes not in the cached library, gjh() falls back to the parser. The
intermediate expanded graph is also available via gjh_graph(), useful for
debugging new codes.
G = gjh_graph('4-3,3-0,4,3/r/r(h2)/r(h18)', bbox_size=16)
G.show(face_inset=0.05, render_vertices=False)