Skip to the plate
FORMA PUBLIC DOMAIN GENERATIVE ATLAS / ED. 0.28
Plate 106, Rabinovich–Fabrikant System: a still of the flow / wave interaction plate as the atlas renders it, in the attractors accent.

PL. 106  ·  ATTRACTORS / FLOW / WAVE INTERACTION

Rabinovich–Fabrikant System

Mikhail Rabinovich & Anatoly Fabrikant, 1979

OPEN THE LIVE PLATE ▸

DEFINITION

ẋ = y(z − 1 + x²) + γ·x
ẏ = x(3z + 1 − x²) + γ·y
ż = −2z(α + xy)

NOTES

Written to describe waves misbehaving in a nonequilibrium plasma, and notorious among the classic flows for how it misbehaves itself: the system is multistable, with limit cycles and chaotic wings coexisting at one parameter set, and outside a narrow window trajectories leave for infinity. That fragility is the exhibit. The published pair sits inside the window, the sliders stop where measurement says the window does, and the integrator still carries an escape guard — a system this close to the edge deserves one on principle.

PROVENANCE

Origin
M. I. Rabinovich & A. L. Fabrikant, "Stochastic self-oscillations of waves in nonequilibrium media", Soviet Physics JETP 50(2), 1979, 311–317
Standing
Public domain — a system of differential equations from Soviet-era physics. Pre-DOI journal; no identifier is invented
Constants
α = 0.14, γ = 0.10 is the celebrated multi-wing set. Both ranges were measured by integrating the declared box: every corner holds a bounded attractor, and the escape guard has the last word if a future edit widens them

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. 106 · RABINOVICH–FABRIKANT SYSTEM — Mikhail Rabinovich & Anatoly Fabrikant, 1979
//   ẋ = y(z − 1 + x²) + γ·x
//   ẏ = x(3z + 1 − x²) + γ·y
//   ż = −2z(α + xy)
// 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=rabinovich

float p_alpha = 0.14 + chf('alpha_tweak');    // α — dissipation · live 0.1 .. 0.25
float p_gamma = 0.1 + chf('gamma_tweak');     // γ — pumping · live 0.05 .. 0.13

// 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.0025 — half the usual, because
// the cubic terms make the flow stiff near the wing tips — 36000 steps, the
// first 600 discarded. The escape guard stops integrating rather than letting
// one runaway coordinate drag the centroid and the scale with it: outside a
// narrow parameter window this system leaves for infinity, and the guard is
// the port telling the truth about that.
int   forma_steps = 36000;
int   forma_skip  = 600;
float forma_dt    = 0.0025;

float x = -1.0, y = 0.0, z = 0.5;
vector pts[];
for (int i = 0; i < forma_steps; i++){
    float dx = y * (z - 1.0 + x * x) + p_gamma * x;
    float dy = x * (3.0 * z + 1.0 - x * x) + p_gamma * y;
    float dz = -2.0 * z * (p_alpha + x * y);
    x += dx * forma_dt;  y += dy * forma_dt;  z += dz * forma_dt;
    if (x * x + y * y + z * z > 1600.0) break;
    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));
    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 _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. 106 · RABINOVICH–FABRIKANT SYSTEM — Mikhail Rabinovich & Anatoly Fabrikant, 1979
//   ẋ = y(z − 1 + x²) + γ·x
//   ẏ = x(3z + 1 − x²) + γ·y
//   ż = −2z(α + xy)
// 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=rabinovich

// 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_alpha = 0.14 + forma_tweak("alpha_tweak");  // α — dissipation · live 0.1 .. 0.25
var p_gamma = 0.1 + forma_tweak("gamma_tweak");   // γ — pumping · live 0.05 .. 0.13
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.3871356281451881;   // 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 Rabinovich–Fabrikant system, the plate's own
// 36,000 steps of dt = 0.0025 from (−1, 0, 0.5), the first 600 discarded. dt
// is half of Lorenz's because the cubic terms make the flow stiff near the
// wing tips, and the escape guard stops integrating rather than letting one
// runaway coordinate drag the centroid with it — a truncated path draws, a
// poisoned one renders nothing. Centred on the path's own centroid. The page's
// view: the x–y plane turns at 0.09 rad/s, z is the screen vertical, 1/6.2 of
// the shorter side — the wings are flat, so the safe 1/7.5 left the typical
// view small. Every second point is emitted.
var x = -1, y = 0, z = 0.5, dt = 0.0025;
var raw = [], mx = 0, my = 0, mz = 0;
for (var i = 0; i < 36000; i++){
  var dx = y * (z - 1 + x * x) + p_gamma * x;
  var dy = x * (3 * z + 1 - x * x) + p_gamma * y;
  var dz = -2 * z * (p_alpha + x * y);
  x += dx * dt; y += dy * dt; z += dz * dt;
  if (x * x + y * y + z * z > 1600) break;
  if (i > 600 && (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.09 + p_view * DEG, co = Math.cos(rot), si = Math.sin(rot);
var s = Math.min(forma_W, forma_H) / 6.2, 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);