Skip to the plate
FORMA PUBLIC DOMAIN GENERATIVE ATLAS / ED. 0.28
Plate 54, Ford Circles: a still of the number theory / tangent circles plate as the atlas renders it, in the lattices accent.

PL. 54  ·  LATTICES / NUMBER THEORY / TANGENT CIRCLES

Ford Circles

Lester R. Ford, 1938 · after John Farey, 1816

OPEN THE LIVE PLATE ▸

DEFINITION

for p/q in lowest terms:
centre (p/q, 1/2q²),  radius 1/2q²
tangent ⇔ |p·q′ − p′·q| = 1

NOTES

Above every rational number sits a circle, tangent to the number line, its size falling with the square of the denominator. Ford noticed the miracle: no two circles ever overlap, and two touch exactly when their fractions are Farey neighbours — the whole arithmetic of mediants drawn as geometry. Between any two touching circles there is always a third, smaller one, forever, which is the number line telling you it is dense. The modular maps that permute these circles by Farey neighbour are Möbius transformations — the family plate 137 draws as a projection and plate 85 folds a whole tiling through.

PROVENANCE

Origin
L. R. Ford, "Fractions", The American Mathematical Monthly 45(9), 1938; the fraction sequences are J. Farey, Philosophical Magazine, 1816
Standing
Public domain — number theory
Constants
Depth Q admits every reduced fraction with denominator up to Q
Source
doi:10.1080/00029890.1938.11990863

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. 54 · FORD CIRCLES — Lester R. Ford, 1938 · after John Farey, 1816
//   for p/q in lowest terms:
//   centre (p/q, 1/2q²),  radius 1/2q²
//   tangent ⇔ |p·q′ − p′·q| = 1
// 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=ford

float p_depth = 20 + chf('depth_tweak');      // Q — max denominator · live 12 .. 32

// The plate's own colour: FORMA's LATTICES 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.4185 + 0.4549 * cos(6.28318530718 * (t + 0.05)),
    0.1389 + 0.151 * cos(6.28318530718 * (t + 0.1)));
}

// Ford's circles complete to depth Q: every reduced p/q with q ≤ Q gets its
// circle at (p/q, 1/2q²) with radius 1/2q², tangent to the number line and
// to every Farey neighbour. Generated coarse denominators first, exactly the
// plate's own Farey-generation order, though a finished cook shows them all.
// The plate's geometry already runs y upward from its baseline — it
// subtracts the radius on canvas — so nothing is negated here: the line is
// y = 0 and the circles sit above it, as they do in Ford's own figure.
// The plate's faint whole-lattice underlay and its HILITE reveal head are
// the animation's legibility devices, not the finished figure, and are not
// ported. Deterministic by construction — there is nothing random here.
int forma_seg = 48;

int Q = int(rint(p_depth));
float TAU = 6.28318530718;

// the number line itself, carried at the plate's own faint alpha
vector lc = forma_ramp(0.0);
int l0 = addpoint(0, set(-0.02, 0.0, 0.0));
int l1 = addpoint(0, set( 1.02, 0.0, 0.0));
setpointattrib(0, "Cd", l0, lc);  setpointattrib(0, "Cd", l1, lc);
setpointattrib(0, "Alpha", l0, 0.3);  setpointattrib(0, "Alpha", l1, 0.3);
addprim(0, "polyline", l0, l1);

for (int q = 1; q <= Q; q++){
    for (int pp = 0; pp <= q; pp++){
        // reduced fractions only: gcd(pp, q) == 1, unrolled — VEX has no recursion
        int x = pp, y = q;
        while (y != 0){ int t = y;  y = x % y;  x = t; }
        if (x != 1) continue;
        float r = 1.0 / (2.0 * float(q) * float(q));
        float cx = float(pp) / float(q);
        // denominator maps onto the bright lobe, the plate's own colouring
        vector col = forma_ramp(0.86 + 0.22 * float(q) / float(Q));
        int prim = addprim(0, "polyline");
        for (int i = 0; i <= forma_seg; i++){
            float th = TAU * float(i) / float(forma_seg);
            int pt = addpoint(0, set(cx + r * cos(th), r + r * sin(th), 0.0));
            setpointattrib(0, "Cd", pt, col);
            setpointattrib(0, "Alpha", pt, 0.85);
            addvertex(0, prim, pt);
        }
    }
}

AFTER EFFECTS · EXPRESSION

The same published mathematics as a Shape Layer path expression. Paste it onto a Path property; every constant is the published value plus a Slider Control named _tweak, so a bare paste already draws the figure and each slider moves one constant in its own units. Trim Paths is the comet.

// FORMA — PL. 54 · FORD CIRCLES — Lester R. Ford, 1938 · after John Farey, 1816
//   for p/q in lowest terms:
//   centre (p/q, 1/2q²),  radius 1/2q²
//   tangent ⇔ |p·q′ − p′·q| = 1
// After Effects port — paste onto a Shape Layer's Path property
// (Contents › Shape › Path). Written from the published mathematics, not
// adapted from any code. Constants arrive at their published values; add a
// Slider Control (Effect › Expression Controls) named <k>_tweak and that
// constant moves in its own units, starting at 0 — the published figure.
// The plate's comet and its reveal are Trim Paths; the stroke colour is
// FORMA's LATTICES accent, #FFE84D. Animation runs on time.
// https://forma-gen.com/#plate=ford

// A missing slider reads 0, so a bare paste already draws the figure.
function forma_tweak(n){ try { return effect(n)("Slider"); } catch (e){ return 0; } }
var p_depth = 20 + forma_tweak("depth_tweak");    // Q — max denominator · live 12 .. 32

// The frame: the plate's W × H canvas is this comp, with the origin at the
// layer's anchor; canvas y already runs down, as After Effects' does.
var forma_W = thisComp.width, forma_H = thisComp.height, forma_t = time;
var forma_phase = 0.9170530012343079;   // this plate's own fixed phase, as the page has it
function forma_pt(x, y){ return [x - forma_W / 2, y - forma_H / 2]; }

// Ford circles: over every reduced fraction p/q up to the slider's
// denominator, a circle of radius 1/(2q²) tangent to the baseline at p/q.
// Two circles touch exactly when their fractions are Farey neighbours
// (|ps − qr| = 1), and every p/q with q ≥ 2 has two parents in the Farey
// sequence — the neighbours whose mediant it is — so the whole set is a tree
// of tangencies rooted at 0/1, and one path walks it: along the baseline, up
// into 0/1, and depth first through the children at their kisses, each circle
// entered and left at its point of contact with the parent it is the mediant
// of (the one with the larger denominator). The page draws from W·0.02 to
// W·0.98 on a baseline 0.92 of the frame wide, as here.
var Q = Math.round(p_depth), TAU = 6.283185307179586;
var s = Math.min(forma_W * 0.92, forma_H * 0.92);
var x0 = (forma_W - s) / 2, y0 = forma_H / 2 + s / 2;
function forma_gcd(a, b){ return b ? forma_gcd(b, a % b) : a; }
var byKey = {}, all = [];
for (var q = 1; q <= Q; q++){
  for (var p = 0; p <= q; p++){
    if (forma_gcd(p, q) !== 1) continue;
    var r = s / (2 * q * q);
    var c = { p: p, q: q, x: x0 + s * p / q, y: y0 - r, r: r, kids: [] };
    byKey[p + '/' + q] = c; all.push(c);
  }
}
// The parent: the Farey neighbour with the larger denominator. b is the
// inverse of p modulo q, so a/b with a = (pb − 1)/q sits one step below p/q
// and (p − a)/(q − b) one step above; 1/1 hangs from 0/1.
for (var i = 0; i < all.length; i++){
  var c2 = all[i];
  if (c2.q === 1){ if (c2.p === 1) byKey['0/1'].kids.push(c2); continue; }
  var b = 1;
  while ((c2.p * b) % c2.q !== 1) b++;
  var a = (c2.p * b - 1) / c2.q;
  var parent = b >= c2.q - b ? byKey[a + '/' + b] : byKey[(c2.p - a) + '/' + (c2.q - b)];
  parent.kids.push(c2);
}
var pts = [];
function forma_arc(c, a0, a1){
  var n = Math.max(2, Math.ceil(Math.abs(a1 - a0) * c.r / 3));
  for (var j = 0; j < n; j++){
    var th = a0 + (a1 - a0) * j / n;
    pts.push(forma_pt(c.x + c.r * Math.cos(th), c.y + c.r * Math.sin(th)));
  }
}
function forma_walk(c, entry){
  var kids = c.kids.slice();
  for (var k = 0; k < kids.length; k++)
    kids[k].at = ((Math.atan2(kids[k].y - c.y, kids[k].x - c.x) - entry) % TAU + TAU) % TAU;
  kids.sort(function (u, v){ return u.at - v.at; });
  var here = entry;
  for (var m = 0; m < kids.length; m++){
    var to = entry + kids[m].at;
    forma_arc(c, here, to);
    here = to;
    forma_walk(kids[m], to + Math.PI);
  }
  forma_arc(c, here, entry + TAU);
  pts.push(forma_pt(c.x + c.r * Math.cos(entry), c.y + c.r * Math.sin(entry)));
}
pts.push(forma_pt(forma_W * 0.98, y0), forma_pt(forma_W * 0.02, y0), forma_pt(x0, y0));
forma_walk(byKey['0/1'], Math.PI / 2);
createPath(pts, [], [], false);