PL. 134 · CURVES / RANDOM WALK / HEAVY-TAILED STEPS
Lévy Flight
Paul Lévy, 1937 · named by Benoit Mandelbrot, 1982 · sampled via Chambers, Mallows and Stuck, 1976
OPEN THE LIVE PLATE ▸DEFINITION
symmetric α-stable, Chambers-Mallows-Stuck 1976: U ~ Unif(−π/2, π/2), W ~ Exp(1) X(α) = sin(αU) / cos(U)^(1/α) · [cos((1−α)U) / W]^((1−α)/α) α = 1: X = tan(U) (Cauchy) α = 2: X ~ N(0, 2) (Gaussian control) both walks share one (direction, U, W) draw per step; α alone differs
NOTES
Two walks start at the same point and take the same number of steps, even the same sequence of directions, drawn from one shared stream of random numbers. Only the exponent feeding each step length differs. Set it to two and the walk is Gaussian, the ordinary random walk whose reach grows only as the square root of the step count -- diffusion, the textbook case. Lower it and the very same random numbers, carried through the stable-distribution formula Chambers, Mallows and Stuck published in 1976, occasionally return a step length far larger than anything around it: not a flourish of this one run but the defining property of a distribution with infinite variance below an exponent of two. One such leap can carry the flight further than every ordinary step in the walk combined -- superdiffusion, in one picture, a process whose reach is set by its rare extremes rather than by the accumulation of many small moves. Paul Lévy set out the mathematics of these stable laws in 1937, long before anyone drew a walk from them. The name Lévy flight belongs to Benoit Mandelbrot, who coined it in 1982 once the same construction had turned up describing the search paths of foraging animals, the statistics of turbulence, and much else besides. The frame here is fit to a percentile of how far both walks reach rather than to their single largest jump, so a rare outlier is free to run off the edge instead of crushing everything else to a dot at the centre.
PROVENANCE
- Origin
- Paul Lévy, Théorie de l’addition des variables aléatoires, Gauthier-Villars, Paris, 1937 -- the book that sets out the stable distributions (Lévy stable, or alpha-stable, laws): the one family of distributions closed under addition up to rescaling, of which the Gaussian is the single finite-variance member and the Cauchy the best known heavy-tailed one. Pre-DOI; no resolvable identifier exists for a 1937 monograph
- Naming
- The term Lévy flight is Benoit B. Mandelbrot, The Fractal Geometry of Nature, W. H. Freeman, 1982 -- coined there for a random walk whose step lengths are drawn from a heavy-tailed distribution, decades after Lévy set out the underlying mathematics and unconnected to any application Lévy himself had in mind. Also pre-DOI
- Sampling
- The symmetric case of the Chambers-Mallows-Stuck construction: J. M. Chambers, C. L. Mallows, B. W. Stuck, "A Method for Simulating Stable Random Variables", Journal of the American Statistical Association 71(354), 1976, 340-344 -- verified on Crossref against title, author, year and venue (doi:10.1080/01621459.1976.10480344). Implemented from the published formula, not from any existing code: draw U uniform on (-pi/2, pi/2) and W exponential(1), then X(a) = sin(aU)/cos(U)^(1/a) times [cos((1-a)U)/W]^((1-a)/a); at a = 1 that expression has a removable singularity whose limit -- also given in the paper -- is the Cauchy variate tan(U)
- Standing
- Public domain -- a probability distribution and a sampling formula, neither patentable
- The shared stream
- Both walks read the same sequence of (direction, U, W) triples at every step; only the exponent handed to the sampling formula differs, alpha for the flight and the fixed value 2 for the control. Checked directly: at alpha = 2 the two computed paths come out identical to the pixel, since stable index 2 is the Gaussian by construction and the control is that same formula evaluated on that same input. Anything the two paths do differently below alpha = 2 is therefore attributable to alpha alone, not to two walks that merely share a seed
- Fit and the coordinate cap
- The frame fits the 92nd percentile of both walks distances from the origin, pooled, computed once against the finished walk -- not the true maximum, and not the revealed prefix. Measured over the declared box: the true maximum typically sits 1.1-1.4x the 92nd percentile, but at alpha = 1 it reached 10x on one seed in twenty, and in a further 270-seed sample at that same corner exactly one walk needed the coordinate cap at all -- its unclamped excursion was 9779px against a fitted window of a few hundred. A linear fit to the true maximum would have rendered that one walk as a dot and a line; fitting to a percentile keeps the ordinary texture of both walks on screen in the other 269 cases out of 270 and lets the genuine outlier run off the edge of the frame instead, which is what an unbounded tail actually does. The cap itself is measured headroom, not a tuned value -- it is reached only by exactly the rare event it exists for
- Constants
- alpha is the only constant this plate exists to show, so its range (1 to 2) runs from Cauchy, the most heavy-tailed case with a clean closed form, to Gaussian, where the flight and the control coincide exactly. Below 1 a stable law has no finite mean even in principle and the walks reach hundreds of thousands of pixels across at the same step count -- mathematically legitimate but a different, more extreme exhibit than the one this plate makes, so the slider stops at 1. count (the matched step count) and rate (its reveal pace) were swept the same way as alpha: the full declared box plus the explicit corners, 180-plus trials, is live throughout with zero throws and zero non-finite coordinates, so neither is a fragility LOCKED needs to guard against. rate is locked anyway, the same way buffon locks its own drops-per-shot pacing dial: it paces the reveal and never changes what is drawn
- Source
- doi:10.1080/01621459.1976.10480344
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. 134 · LÉVY FLIGHT — Paul Lévy, 1937 · named by Benoit Mandelbrot, 1982 · sampled via Chambers, Mallows and Stuck, 1976
// symmetric α-stable, Chambers-Mallows-Stuck 1976:
// U ~ Unif(−π/2, π/2), W ~ Exp(1)
// X(α) = sin(αU) / cos(U)^(1/α) · [cos((1−α)U) / W]^((1−α)/α)
// α = 1: X = tan(U) (Cauchy) α = 2: X ~ N(0, 2) (Gaussian control)
// both walks share one (direction, U, W) draw per step; α alone differs
// 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=levyflight
float p_alpha = 1.3 + chf('alpha_tweak'); // α — stability exponent · live 1 .. 2
float p_count = 400 + chf('count_tweak'); // steps per walk · live 150 .. 700
// The plate's own colour: FORMA's CURVES 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.11 + 0.1196 * cos(6.28318530718 * (t + 0)),
0.46 + 0.5 * cos(6.28318530718 * (t + 0.05)),
0.2453 + 0.2667 * cos(6.28318530718 * (t + 0.1)));
}
// Chambers-Mallows-Stuck 1976, symmetric case, cooked as two matched walks
// sharing one random stream: at every step both walks read the same
// direction and the same (U, Wexp) pair, and only the exponent handed to
// the sampling formula differs -- p_alpha for the flight, the fixed value
// 2 for the Gaussian control. That shared stream is the whole point:
// anything the two paths do differently is attributable to the exponent
// alone, never to two walks that merely share a seed -- checked on the
// page side by confirming the two paths come out identical to the pixel
// at alpha = 2. Emitted as two polylines at their raw scale: the page
// canvas needs a percentile fit and a coordinate cap to keep one rare
// enormous jump from swamping a fixed frame, but a Houdini scene has no
// fixed frame, so this cook skips both devices and leaves each walk at
// the scale the mathematics itself produces -- frame the view to taste.
// Canvas y runs down; this cook negates y to match Houdini's own up, the
// corpus convention.
// waived: rate — it paces the plate's on-page reveal, and a cook has no
// clock to pace; alpha and count both reach the body
function float forma_stable(float U; float Wexp; float a){
// a = 1 is a removable singularity in the general formula whose own
// limit is the Cauchy case tan(U) -- CMS give this branch themselves,
// it is not an approximation invented for the port.
if (abs(a - 1.0) < 1e-9) return tan(U);
return sin(a * U) / pow(cos(U), 1.0 / a)
* pow(cos((1.0 - a) * U) / Wexp, (1.0 - a) / a);
}
int n = int(rint(p_count));
int rc = 4001; // the plate's own seed, counted upward
int lprim = addprim(0, "polyline");
int gprim = addprim(0, "polyline");
vector lcol = forma_ramp(1.02);
vector gcol = forma_ramp(0.80);
int lp0 = addpoint(0, set(0.0, 0.0, 0.0));
setpointattrib(0, "Cd", lp0, lcol);
setpointattrib(0, "Alpha", lp0, 0.9);
addvertex(0, lprim, lp0);
int gp0 = addpoint(0, set(0.0, 0.0, 0.0));
setpointattrib(0, "Cd", gp0, gcol);
setpointattrib(0, "Alpha", gp0, 0.5);
addvertex(0, gprim, gp0);
float lx = 0.0, ly = 0.0, gx = 0.0, gy = 0.0;
for (int i = 0; i < n; i++){
float theta = random(rc) * 2.0 * M_PI; rc++;
float U = (random(rc) - 0.5) * M_PI; rc++;
// floored away from 0, the same guard the page draw applies: a
// literal-zero draw sends Wexp to +infinity through -log, which
// blows up the second factor above alpha = 1 rather than leaving
// the tail to do that honestly through U alone.
float Wexp = -log(max(random(rc), 1e-9)); rc++;
float rL = abs(forma_stable(U, Wexp, p_alpha));
float rG = abs(forma_stable(U, Wexp, 2.0));
float co = cos(theta), si = sin(theta);
lx += rL * co; ly += rL * si;
gx += rG * co; gy += rG * si;
int lpt = addpoint(0, set(lx, -ly, 0.0));
setpointattrib(0, "Cd", lpt, lcol);
setpointattrib(0, "Alpha", lpt, 0.9);
addvertex(0, lprim, lpt);
int gpt = addpoint(0, set(gx, -gy, 0.0));
setpointattrib(0, "Cd", gpt, gcol);
setpointattrib(0, "Alpha", gpt, 0.5);
addvertex(0, gprim, gpt);
}
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. 134 · LÉVY FLIGHT — Paul Lévy, 1937 · named by Benoit Mandelbrot, 1982 · sampled via Chambers, Mallows and Stuck, 1976
// symmetric α-stable, Chambers-Mallows-Stuck 1976:
// U ~ Unif(−π/2, π/2), W ~ Exp(1)
// X(α) = sin(αU) / cos(U)^(1/α) · [cos((1−α)U) / W]^((1−α)/α)
// α = 1: X = tan(U) (Cauchy) α = 2: X ~ N(0, 2) (Gaussian control)
// both walks share one (direction, U, W) draw per step; α alone differs
// 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 CURVES accent, #3DFF88. Animation runs on time.
// This plate draws 2 separate paths at its published constants:
// duplicate the group (Contents › Group) that many times and each copy draws
// its own part, read from its position in the layer. A Slider Control named
// "part" on the layer pins one instead.
// The figure is seeded like the page: a bare paste is the page's boot seed,
// and a Slider Control named seed_tweak moves to any other.
// https://forma-gen.com/#plate=levyflight
// 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 = 1.3 + forma_tweak("alpha_tweak"); // α — stability exponent · live 1 .. 2
var p_count = 400 + forma_tweak("count_tweak"); // steps per walk · live 150 .. 700
// 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.8817198013421148; // 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]; }
function forma_partIndex(){
try { return Math.round(effect("part")("Slider")); } catch (e){}
try { return thisProperty.propertyGroup(3).propertyIndex - 1; } catch (e){ return 0; }
}
var forma_part = forma_partIndex();
var forma_seed = (1 + Math.round(forma_tweak("seed_tweak"))) >>> 0;
var forma_imul = Math.imul || function (a, b){
var ah = (a >>> 16) & 0xffff, al = a & 0xffff, bh = (b >>> 16) & 0xffff, bl = b & 0xffff;
return (al * bl + (((ah * bl + al * bh) << 16) >>> 0)) | 0;
};
function forma_seeded(k){ // the page's seeded(k): mulberry32 on the atlas seed
var a = (((forma_seed * 2654435761) >>> 0) ^ ((k * 40503) >>> 0)) >>> 0;
return function (){
a = a + 0x6D2B79F5 | 0;
var t = forma_imul(a ^ a >>> 15, 1 | a);
t = t + forma_imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
// A Lévy flight beside its Gaussian control: the same `count` steps in the
// same directions, with step lengths drawn from a stable law of index α by
// the Chambers–Mallows–Stuck formula — X = sin(αU)/cos(U)^(1/α) ·
// (cos((1−α)U)/W)^((1−α)/α) for U uniform on (−π/2, π/2) and W exponential —
// and, for the control, the same U and W at α = 2, which is Gaussian. One
// generator feeds both walks so α is the only thing that can make them
// diverge; it is the page's own seeded(4001). The frame is fitted to the
// 92nd percentile of both walks' distances from the origin, so a rare jump
// runs off the edge rather than crushing the texture, and any point beyond
// eight frame widths is clamped there. Part 0 is the flight, part 1 the
// Gaussian control the page draws dimmer beside it.
// waived: rate — it paces the page's regrow cycle; Trim Paths is the reveal
// parts: 2
var TAU = 6.283185307179586, count = Math.round(p_count), alpha = p_alpha;
var cx = forma_W / 2, cy = forma_H / 2;
function forma_stable(U, Wexp, a){
if (Math.abs(a - 1) < 1e-9) return Math.tan(U);
return Math.sin(a * U) / Math.pow(Math.cos(U), 1 / a)
* Math.pow(Math.cos((1 - a) * U) / Wexp, (1 - a) / a);
}
var rng = forma_seeded(4001);
var lx = 0, ly = 0, gx = 0, gy = 0;
var lraw = [[0, 0]], graw = [[0, 0]];
for (var i = 0; i < count; i++){
var theta = rng() * TAU;
var U = (rng() - 0.5) * Math.PI;
var Wexp = -Math.log(Math.max(rng(), 1e-9));
var rL = Math.abs(forma_stable(U, Wexp, alpha)), rG = Math.abs(forma_stable(U, Wexp, 2));
if (!isFinite(rL)) rL = 0;
if (!isFinite(rG)) rG = 0;
var co = Math.cos(theta), si = Math.sin(theta);
lx += rL * co; ly += rL * si;
gx += rG * co; gy += rG * si;
lraw.push([lx, ly]); graw.push([gx, gy]);
}
var radii = [];
for (var j = 0; j <= count; j++){
radii.push(Math.sqrt(lraw[j][0] * lraw[j][0] + lraw[j][1] * lraw[j][1]));
radii.push(Math.sqrt(graw[j][0] * graw[j][0] + graw[j][1] * graw[j][1]));
}
radii.sort(function (a, b){ return a - b; });
var win = radii[Math.floor(radii.length * 0.92)] || 1;
var sc = Math.min(forma_W, forma_H) * 0.38 / win;
var cap = Math.max(forma_W, forma_H) * 8;
var walk = forma_part === 1 ? graw : lraw;
var pts = [];
for (var m = 0; m < walk.length; m++){
var x = Math.max(cx - cap, Math.min(cx + cap, cx + walk[m][0] * sc));
var y = Math.max(cy - cap, Math.min(cy + cap, cy + walk[m][1] * sc));
pts.push(forma_pt(x, y));
}
createPath(pts, [], [], false);