PL. 78 · ATTRACTORS / FLOW / TORUS BIFURCATION
Langford Attractor
William F. Langford, 1984
OPEN THE LIVE PLATE ▸DEFINITION
ẋ = (z − b)x − d·y ẏ = d·x + (z − b)y ż = c + a·z − z³/3 − (x² + y²)(1 + e·z) + f·z·x³
NOTES
A torus that folds into a shell — the trajectory winds around a rising bowl, turns over its rim and drops back through the middle. It is all over the internet under the name "Aizawa", and that attribution appears to have no paper behind it: the equations are not in Aizawa’s published work. They are in Langford’s 1984 study of torus bifurcations, which is where this plate credits them. The name a figure travels under and the name on the mathematics are not always the same thing, and when they differ this atlas prints the second.
PROVENANCE
- Origin
- W. F. Langford, "Numerical studies of torus bifurcations", International Series of Numerical Mathematics, 1984
- Standing
- Public domain — a system of differential equations
- Constants
- a = 0.95, b = 0.7, c = 0.6, d = 3.5, e = 0.25, f = 0.1 is the set the figure is usually drawn from. e and f are LOCKED from regenerate: they are small shape terms, and f in particular is what folds the rim — jitter there flattens the shell into a plain torus
- Source
- doi:10.1007/978-3-0348-6256-1_19
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. 78 · LANGFORD ATTRACTOR — William F. Langford, 1984
// ẋ = (z − b)x − d·y
// ẏ = d·x + (z − b)y
// ż = c + a·z − z³/3 − (x² + y²)(1 + e·z) + f·z·x³
// 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=langford
float p_a = 0.95 + chf('a_tweak'); // a — z gain · live 0.8 .. 1.05
float p_b = 0.7 + chf('b_tweak'); // b — radius · live 0.55 .. 0.85
float p_c = 0.6 + chf('c_tweak'); // c — lift · live 0.45 .. 0.75
float p_d = 3.5 + chf('d_tweak'); // d — rotation · live 2.6 .. 4.4
float p_e = 0.25 + chf('e_tweak'); // e — coupling · live 0.18 .. 0.32
float p_f = 0.1 + chf('f_tweak'); // f — fold · live 0.04 .. 0.16
// The plate's own colour: FORMA's ATTRACTORS 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.0956 + 0.1039 * cos(6.28318530718 * (t + 0)),
0.4041 + 0.4392 * cos(6.28318530718 * (t + 0.05)),
0.46 + 0.5 * cos(6.28318530718 * (t + 0.1)));
}
// waived: view — the azimuth the finished attractor is viewed from, and Houdini has a camera of its own
// Euler integration matching the plate: dt = 0.004, 34000 steps from
// (0.1, 0, 0), the first 4000 discarded as transient. The system rotates
// about its z axis, conventionally vertical; mapped onto Houdini's y-up.
int forma_steps = 34000;
int forma_skip = 4000;
float forma_dt = 0.004;
float x = 0.1, y = 0.0, z = 0.0;
int prim = addprim(0, "polyline");
for (int i = 0; i < forma_steps; i++){
float dx = (z - p_b) * x - p_d * y;
float dy = p_d * x + (z - p_b) * y;
float dz = p_c + p_a * z - z * z * z / 3.0
- (x * x + y * y) * (1.0 + p_e * z) + p_f * z * x * x * x;
x += dx * forma_dt; y += dy * forma_dt; z += dz * forma_dt;
if (!isfinite(x) || !isfinite(z)) break;
if (i <= forma_skip) continue;
int pt = addpoint(0, set(x, z, y));
// colour sweeps the bright lobe of the ramp along the path
float u = float(i - forma_skip) / float(forma_steps - forma_skip);
setpointattrib(0, "Cd", pt, forma_ramp(0.8 + 0.3 * u));
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
// FORMA — PL. 78 · LANGFORD ATTRACTOR — William F. Langford, 1984
// ẋ = (z − b)x − d·y
// ẏ = d·x + (z − b)y
// ż = c + a·z − z³/3 − (x² + y²)(1 + e·z) + f·z·x³
// 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 ATTRACTORS accent, #35E0FF. Animation runs on time.
// https://forma-gen.com/#plate=langford
// 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_a = 0.95 + forma_tweak("a_tweak"); // a — z gain · live 0.8 .. 1.05
var p_b = 0.7 + forma_tweak("b_tweak"); // b — radius · live 0.55 .. 0.85
var p_c = 0.6 + forma_tweak("c_tweak"); // c — lift · live 0.45 .. 0.75
var p_d = 3.5 + forma_tweak("d_tweak"); // d — rotation · live 2.6 .. 4.4
var p_e = 0.25 + forma_tweak("e_tweak"); // e — coupling · live 0.18 .. 0.32
var p_f = 0.1 + forma_tweak("f_tweak"); // f — fold · live 0.04 .. 0.16
var p_view = 0 + forma_tweak("view_tweak"); // view azimuth ° · live -180 .. 180
// 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.513494668295607; // 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]; }
// Euler integration of the Langford (Aizawa) system, the plate's own 34,000
// steps of dt = 0.004 from (0.1, 0, 0), the first 4,000 discarded, the shell
// lowered by 1.1 so it sits centred on its own axis. The page's view: the x–y
// plane turns at 0.09 rad/s, z is the screen vertical, 1/3.4 of the shorter
// side. Every second point is emitted.
var x = 0.1, y = 0, z = 0, dt = 0.004;
var DEG = Math.PI / 180;
var rot = forma_t * 0.09 + p_view * DEG, co = Math.cos(rot), si = Math.sin(rot);
var s = Math.min(forma_W, forma_H) / 3.4, cx = forma_W / 2, cy = forma_H / 2;
var pts = [];
for (var i = 0; i < 34000; i++){
var dx = (z - p_b) * x - p_d * y;
var dy = p_d * x + (z - p_b) * y;
var dz = p_c + p_a * z - z * z * z / 3 - (x * x + y * y) * (1 + p_e * z) + p_f * z * x * x * x;
x += dx * dt; y += dy * dt; z += dz * dt;
if (!isFinite(x) || !isFinite(z)) break;
if (i > 4000 && (i & 1) === 0){
var zz = z - 1.1;
pts.push(forma_pt(cx + (x * co + y * si) * s, cy - zz * s));
}
}
createPath(pts, [], [], false);