PL. 105 · ATTRACTORS / FLOW / CYCLIC SYMMETRY
Halvorsen Attractor
Arne Dehli Halvorsen · documented by J. C. Sprott, 2003
OPEN THE LIVE PLATE ▸DEFINITION
ẋ = −a·x − 4y − 4z − y² ẏ = −a·y − 4z − 4x − z² ż = −a·z − 4x − 4y − x²
NOTES
One equation written three times, each a cyclic shuffle of the last: x feeds y feeds z feeds x, with nothing to break the symmetry. The attractor inherits it — three intertwined lobes that map onto each other under a one-third turn about the diagonal — which makes this the most evenly balanced of the classic chaotic flows under rotation. Halvorsen proposed the system informally on the sci.fractals newsgroup; it entered the record when Sprott catalogued it, and it has been a favourite of the strange-attractor galleries since. The damping a is the one dial: low and the lobes fatten toward merging, high and the flow collapses to a point.
PROVENANCE
- Origin
- Proposed by Arne Dehli Halvorsen on the sci.fractals newsgroup; documented in J. C. Sprott, Chaos and Time-Series Analysis, Oxford University Press, 2003
- Standing
- Public domain — a system of differential equations circulated openly. No patent. Unpublished in a journal, so there is no DOI to cite, and none is invented
- Constants
- a = 1.4 is the value the catalogues draw; the range was measured live here — below about 1.25 the trajectory escapes and above 2.05 it spirals to the origin
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. 105 · HALVORSEN ATTRACTOR — Arne Dehli Halvorsen · documented by J. C. Sprott, 2003
// ẋ = −a·x − 4y − 4z − y²
// ẏ = −a·y − 4z − 4x − z²
// ż = −a·z − 4x − 4y − 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=halvorsen
float p_a = 1.4 + chf('a_tweak'); // a — damping · live 1.3 .. 2
// 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.005, 20000 steps, the first
// 400 discarded — the walk toward the attractor is not the attractor. The
// path is centred on its own centroid, as the plate centres it: the cyclic
// symmetry axis is the space diagonal, so no hand-picked offset is honest.
int forma_steps = 20000;
int forma_skip = 400;
float forma_dt = 0.005;
float x = -1.48, y = -1.51, z = 2.04;
vector pts[];
for (int i = 0; i < forma_steps; i++){
float dx = -p_a * x - 4.0 * y - 4.0 * z - y * y;
float dy = -p_a * y - 4.0 * z - 4.0 * x - z * z;
float dz = -p_a * z - 4.0 * x - 4.0 * y - x * x;
x += dx * forma_dt; y += dy * forma_dt; z += dz * forma_dt;
if (i <= forma_skip) continue;
append(pts, set(x, y, z));
}
vector centre = {0, 0, 0};
foreach (vector q; pts) centre += q;
centre /= float(len(pts));
// z-up ODE mapped onto Houdini's y-up, as every port here maps it.
int prim = addprim(0, "polyline");
for (int i = 0; i < len(pts); i++){
vector q = pts[i] - centre;
int pt = addpoint(0, set(q.x, q.z, q.y));
// colour sweeps the bright lobe of the ramp along the path
float u = float(i) / float(len(pts));
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. 105 · HALVORSEN ATTRACTOR — Arne Dehli Halvorsen · documented by J. C. Sprott, 2003
// ẋ = −a·x − 4y − 4z − y²
// ẏ = −a·y − 4z − 4x − z²
// ż = −a·z − 4x − 4y − 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=halvorsen
// 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 = 1.4 + forma_tweak("a_tweak"); // a — damping · live 1.3 .. 2
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.8017201954498887; // 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 Halvorsen's cyclically symmetric system, the plate's
// 20,000 steps of dt = 0.005 from (−1.48, −1.51, 2.04), the first 400
// discarded, centred on the path's own centroid — the symmetry axis is the
// space diagonal, so no hand-picked offset would be honest. The page's view:
// the x–y plane turns at 0.11 rad/s, z is the screen vertical, 1/22 of the
// shorter side. Every second point is emitted.
var x = -1.48, y = -1.51, z = 2.04, dt = 0.005;
var raw = [], mx = 0, my = 0, mz = 0;
for (var i = 0; i < 20000; i++){
var dx = -p_a * x - 4 * y - 4 * z - y * y;
var dy = -p_a * y - 4 * z - 4 * x - z * z;
var dz = -p_a * z - 4 * x - 4 * y - x * x;
x += dx * dt; y += dy * dt; z += dz * dt;
if (i > 400 && (i & 1) === 0){ raw.push([x, y, z]); mx += x; my += y; mz += z; }
}
mx /= raw.length; my /= raw.length; mz /= raw.length;
var DEG = Math.PI / 180;
var rot = forma_t * 0.11 + p_view * DEG, co = Math.cos(rot), si = Math.sin(rot);
var s = Math.min(forma_W, forma_H) / 22, cx = forma_W / 2, cy = forma_H / 2;
var pts = [];
for (var j = 0; j < raw.length; j++){
var q = raw[j], qx = q[0] - mx, qy = q[1] - my, qz = q[2] - mz;
pts.push(forma_pt(cx + (qx * co + qy * si) * s, cy - qz * s));
}
createPath(pts, [], [], false);