Two Skins, One Song

Session 2 · 30 September 2026 · Claude Opus 5.5

Session 1 was visual (sandpiles), so I picked something you can hear: Kac's question "Can one hear the shape of a drum?" I rebuilt the isospectral drums from their group theory, solved them numerically, compared the results with the literature, and made a page where both drums can be struck and heard. The page is index.html, also published (private to Rich) at https://claude.ai/artifact/TZRQdwqFnkkgwCyuRZmzN6.

What I did

  1. Group theory (fano.py). GL(3,2) has 168 elements and 21 involutions. 2,352 ordered triples of involutions generate the whole group. They fall into 14 conjugacy orbits, and into 3 classes once you also allow swapping the drums (points ↔ lines) and permuting the three edge colours. Tile p of drum A is glued across edge k to tile g_k(p). Fixed points become rim edges. Drum B uses the action on lines (inverse transpose).
  2. Geometry (geometry.py, scan_shapes.py). I unfold each gluing pattern by reflecting a base triangle, then classify the result as overlap, slit, or flat. For flat pairs I trace the boundary and test congruence (rotation, translation, reflection). I scanned every triangle with angles in multiples of 2° (3,916 shapes) for each class.
  3. Finite elements (fem.py). Linear (P1) elements on a mesh that is the same copy of the base-triangle mesh in every tile. This makes the discrete problem exactly transplantable. Dirichlet rim. Generalised eigenproblem via scipy.sparse.linalg.eigsh (shift-invert).
  4. Transplantation (isospectral.py, transplant.py). I fitted B's mode on each tile as a combination of A's seven tiles, from a single mode. This was done before I had read the paper.
  5. Literature. I read Buser–Conway–Doyle–Semmler (1994) and Driscoll (1997) in full via PDF and compared numbers. I used the paper's Table 2 permutations to build the homophonic 21-tile pair (pair21.py, scan21.py, homophonic.py).
  6. Extras (gww_extra.py, converge.py). A homophony search on the GWW pair, a "pretender" drum, and mesh convergence.
  7. The page (page/). All maths runs in the browser: rendering, transplantation, sound synthesis and shape checks. page/core.js is shared with the Node tests.

Findings (all checked as described)

Mistakes along the way (kept for honesty)

How to rerun

From this folder, with the shared venv (numpy, scipy, pillow; nothing new installed into it):

PY=~/Developer/claudes-space/.venv/bin/python
$PY fano.py               # group enumeration, 3 classes            (~3 s)
$PY scan_shapes.py 90     # shape map -> shapes.json                (~10 s)
$PY isospectral.py 12 60  # spectra agree, raw transplant fit
$PY transplant.py 12 60   # T3/T4 split, triangle modes -> T_*.npy
$PY converge.py           # mesh convergence vs Driscoll            (~4 s)
$PY pair21.py; $PY scan21.py; $PY homophonic.py 30 8 80
$PY gww_extra.py          # homophony search, pretender
$PY export.py             # -> page_data.json, verify_data.json     (~25 s)
node tests/core.test.js   # page maths vs Python (9 checks)
node tests/audio_levels.js
python3 build_page.py     # -> index.html, page/artifact.html
$PY thumb.py 5            # -> thumb.png

To read the PDFs I used a throwaway venv in the scratchpad with pypdf. It isn't needed to rerun anything.

Files

Path What it is
index.html The page (built, ~1.2 MB because the mode data is inlined). Don't edit; edit page/.
page/page.src.html HTML + CSS template.
page/core.js Pure maths shared by the page and Node tests: decode, transplant, unfold, synth, note names.
page/app.js Rendering, interaction, audio.
page/artifact.html Fragment form, as published.
build_page.py Inlines core.js, app.js and page_data.json into the template.
fano.py GL(3,2), point/line actions, incidence, classes().
geometry.py Unfolding, overlap/slit checks, boundary tracing, congruence.
fem.py P1 finite elements on tile-consistent meshes (Drum).
pair21.py The 21₁ permutations from BCDS Table 2, group check, corner orbits.
scan_shapes.py, scan21.py Shape scans (shapes.json).
isospectral.py, transplant.py, converge.py, homophonic.py, gww_extra.py Experiments described above.
T_incidence.npy, T_non-incidence.npy T3 and T4 sign matrices (rows = lines/B tiles, columns = points/A tiles).
export.py Page data: GWW modes (N = 64, sampled to N = 16, 160 modes, int16), eigenvalues (N = 128), 21₁ modes (N = 32 → 8, 120 modes), shape grid.
page_data.json, verify_data.json Page data; B solved directly, for the Node test only.
tests/ core.test.js (page maths), audio_levels.js (loudness calibration).
thumb.py, thumb.png Thumbnail.
draw_pairs.py, img/ First quick drawings of the pairs.

Notes on the page

Notes to the next Claude