PL. 101 · FRACTALS / LIMIT SET / MÖBIUS PAIR
Kleinian Group Limit Set
Felix Klein & Henri Poincaré, 1883 · rendering recipe after David Mumford, Caroline Series & David Wright, 2002
OPEN THE LIVE PLATE ▸DEFINITION
A, B ∈ SL(2,ℂ): tr(A) = 2 (parabolic) tr(B) = μ, tr(AB) = μ + 2i (forces tr(ABA⁻¹B⁻¹) = −2, a parabolic commutator)
NOTES
Two Möbius maps generate a free group acting on the sphere; its limit set is the closure of every accumulation point of every orbit under every word in the two maps and their inverses — a fractal curve threaded with infinitely many circles, invariant under the whole group. Fixing one generator as parabolic and forcing the commutator of both to be parabolic too is the classical trace condition used to look for such groups. It is a condition on traces and not a proof of discreteness, and none is offered here: at a given μ this construction may generate a group that is not discrete at all, in which case what is plotted is the closure of one orbit rather than a limit set. What was measured is the picture — box-counting the plotted set across the whole declared box returns dimensions of 0.79 to 1.13, curve-like everywhere and never approaching the 2 an orbit smeared over an open set would give.
PROVENANCE
- Origin
- F. Klein, "Neue Beiträge zur Riemann’schen Functionentheorie", Mathematische Annalen 21(2), 1883, pp.141–218; H. Poincaré, "Mémoire sur les groupes kleinéens", Acta Mathematica 3, 1883, pp.49–92. The two-generator trace parametrisation used to build the plate’s matrices — "Grandma’s Recipe" — is David Mumford, Caroline Series & David Wright, "Indra’s Pearls: The Vision of Felix Klein", Cambridge University Press, 2002, itself built on the classical Fricke trace identity for rank-2 SL(2,ℂ) representations (R. Fricke & F. Klein, "Vorlesungen über die Theorie der automorphen Functionen", 1897).
- Standing
- Public domain — the group theory is 19th century. The trace identity used to fix tr(AB) is a mathematical fact, not expression, so building matrices from it is not adapted from Indra’s Pearls’ own code or figures; the recipe here is derived directly from the identity below, not transcribed from the book.
- What is claimed, and what is not
- Discreteness is not established here. The trace conditions are the classical way to search for quasi-Fuchsian groups, but satisfying them does not prove the group generated is discrete, and this plate runs no such test — so it does not claim to draw a limit set at every μ, only the closure of the orbit it actually walks. What was measured is the render: box-counting the plotted set over a grid of positions in the declared box returns a dimension between 0.79 and 1.13 at every one, so the picture stays a curve-like set across the whole slider range rather than degenerating to the 2 of a filled region.
- Constants
- A is locked at the classical parabolic choice tr(A)=2; μ = tr(B) is the plate’s one free complex parameter, and both sliders move it
HOUDINI · VEX
The same published mathematics as a Detail Wrangle body. Paste it into a Wrangle with Run Over set to Detail; every constant is the published value plus a tweak channel, so Create Spare Parameters gives a slider that starts where the paper does.
// FORMA — PL. 101 · KLEINIAN GROUP LIMIT SET — Felix Klein & Henri Poincaré, 1883 · rendering recipe after David Mumford, Caroline Series & David Wright, 2002
// A, B ∈ SL(2,ℂ): tr(A) = 2 (parabolic)
// tr(B) = μ, tr(AB) = μ + 2i
// (forces tr(ABA⁻¹B⁻¹) = −2, a parabolic commutator)
// Paste into a Detail Wrangle (Run Over: Detail), no inputs needed.
// Written from the published mathematics, not adapted from any code.
// Constants arrive at their published values. Press the node's Create
// Spare Parameters button and every tweak becomes a slider — starting
// at 0, the published figure, and moving in the constant's own units.
// https://forma-gen.com/#plate=kleinian
float p_re = 1.1 + chf('re_tweak'); // Re(μ) — trace of B · live 0.15 .. 2.2
float p_im = 1.7 + chf('im_tweak'); // Im(μ) — trace of B · live 0.4 .. 3
// The plate's own colour: FORMA's FRACTALS accent as a cosine ramp,
// brightest near t = 0 and t = 1, near-black around t = 0.5.
vector forma_ramp(float t){
return set(
0.46 + 0.5 * cos(6.28318530718 * (t + 0)),
0.1389 + 0.151 * cos(6.28318530718 * (t + 0.05)),
0.1912 + 0.2078 * cos(6.28318530718 * (t + 0.1)));
}
// Grandma's Recipe orbit, replayed as a point cloud. A is the parabolic
// translation w+1 and B is solved in closed form from tr(B) = mu and
// tr(AB) = mu + 2i, so the commutator is parabolic — the same matrices the
// plate derives, ported literally, divisions and all. The walk never undoes
// the move it just took, exactly as the plate's walk refuses to; and the
// plate's own caveat travels with it: nothing here proves the group
// discrete, so what is plotted is the closure of the orbit actually walked.
//
// The orbit is unbounded in this chart — rare excursions run toward the
// parabolic cusp at infinity — so the emitted cloud is clipped to the same
// 2nd–98th percentile box, with the same 10% margin, that the plate
// measures for its own frame. Without the clip a handful of far points
// would zoom any viewport out until the necklace vanished. The plate then
// squares that box to its canvas; there is no canvas here, so the box is
// kept as measured. Deterministic: both walks are random(counted seed),
// the measuring walk on the plate's 701, the plotted one on its 709.
int forma_measure = 3000;
int forma_skip = 20;
int forma_pts = 120000;
// one Möbius step: the four generators in the plate's own closed forms
function float[] forma_mobius(float x, y, mr, mi; int m){
float nx, ny;
if (m == 0) { nx = x + 1.0; ny = y; } // A: w + 1
else if (m == 1) { nx = x - 1.0; ny = y; } // A⁻¹
else if (m == 2) { // B: (i/2) / (2i·w + mu)
float dr = mr - 2.0 * y, di = 2.0 * x + mi;
float d2 = dr * dr + di * di;
nx = 0.5 * di / d2; ny = 0.5 * dr / d2;
} else { // B⁻¹: (mu·w − i/2) / (−2i·w)
float nr = mr * x - mi * y, ni = mr * y + mi * x - 0.5;
float dr = 2.0 * y, di = -2.0 * x;
float d2 = dr * dr + di * di;
nx = (nr * dr + ni * di) / d2; ny = (ni * dr - nr * di) / d2;
}
return array(nx, ny);
}
float mr = p_re, mi = p_im;
int opp[] = {1, 0, 3, 2}; // undoing the step just taken retreads old ground
// the measuring walk: where does the orbit actually spend its time
float xs[], ys[];
float x = 0.001, y = 0.4;
int last = -1, rc = 701;
for (int i = 0; i < forma_measure; i++){
int m = 0;
do { m = int(random(rc) * 4); rc++; } while (last >= 0 && m == opp[last]);
float q[] = forma_mobius(x, y, mr, mi, m);
x = q[0]; y = q[1]; last = m;
if (i > 40 && isfinite(x) && isfinite(y)){ push(xs, x); push(ys, y); }
}
float sx[] = sort(xs), sy[] = sort(ys);
int ln = len(sx), lo = int(ln * 0.02), hi = int(ln * 0.98);
float x0 = sx[lo], x1 = sx[hi], y0 = sy[lo], y1 = sy[hi];
float mx = (x1 - x0) * 0.1, my = (y1 - y0) * 0.1;
if (mx == 0.0) mx = 0.5;
if (my == 0.0) my = 0.5;
float bx0 = x0 - mx, bx1 = x1 + mx, by0 = y0 - my, by1 = y1 + my;
// the plotted walk, on its own seed, as the plate's expose() runs it
x = 0.001; y = 0.4; last = -1; rc = 709;
int plotted = 0;
int guard = forma_pts * 2; // hard-bounded so no tweak can hang the cook
for (int i = 0; i < guard; i++){
if (plotted >= forma_pts) break;
int m = 0;
do { m = int(random(rc) * 4); rc++; } while (last >= 0 && m == opp[last]);
float q[] = forma_mobius(x, y, mr, mi, m);
last = m;
// a blown-up step restarts from the seed point rather than plotting inf
if (!isfinite(q[0]) || !isfinite(q[1])){ x = 0.001; y = 0.4; last = -1; continue; }
x = q[0]; y = q[1];
if (i < forma_skip) continue; // the walk toward the set
if (x < bx0 || x > bx1 || y < by0 || y > by1) continue;
// canvas y runs down; negated so the necklace sits as the plate shows it
int pt = addpoint(0, set(x, -y, 0.0));
// colour sweeps the bright lobe of the ramp across the exposure
float u = float(plotted) / float(forma_pts);
setpointattrib(0, "Cd", pt, forma_ramp(0.8 + 0.3 * u));
plotted++;
}