Devlog #87 – Wave 66: Fourier Epicycles, Magnetic Pendulum, Surface Wetting, Gear Train, Knapsack & Huffman

Wave 66 spans a satisfying range of domains: a DFT epicycle renderer that reconstructs any drawn curve as a chain of rotating circles; a magnetic pendulum whose fractal basin-of-attraction map is one of the most beautiful demonstrations of chaos theory; a surface wetting simulator covering Young's equation through to the lotus effect; an animated gear-train builder with compound ratio calculation; a 0/1 knapsack visualiser exposing every cell of the DP table; and a Huffman coding tree that shows prefix codes converging to Shannon entropy. The library reaches 597 simulations.

Wave 66 — 6 simulations added
597
Total simulations
6
New this wave
66
Wave number
87
Devlog #

New Simulations

πŸŒ€

Fourier Epicycles — DFT of a Drawn Path as a Chain of Rotating Circles

Draw any closed curve on the canvas and the simulation computes its complex DFT. Each frequency bin k becomes one epicycle: amplitude |X[k]|, angular frequency k rev/period, initial phase arg(X[k]). Epicycles are sorted by amplitude so the dominant motion dominates visually. Five built-in presets (circle, star, heart, figure-8, lemniscate) let you explore the spectrum immediately.

🧲

Magnetic Pendulum — Fractal Basin Map via RK4 Integration

A pendulum swings above three magnets placed at the vertices of an equilateral triangle. RK4 integration tracks the trajectory under gravity plus three magnetic dipole forces proportional to 1/rΒ³. For each starting position on a pixel grid the simulation runs to rest and records which magnet captured the bob, producing a fractal basin-of-attraction image with infinite boundary detail.

πŸ’§

Surface Wetting — Young, Wenzel, Cassie-Baxter & the Lotus Effect

Interactive droplet shape morphs with the contact-angle slider. Young's equation governs flat surfaces; the Wenzel model adds surface roughness; the Cassie-Baxter model handles composite (air-trapping) surfaces. Superhydrophobic surfaces (ΞΈ > 150Β°) demonstrate the lotus effect. Surface roughness is visualised beneath the droplet.

βš™οΈ

Gear Train — Meshing Pairs, Idler Gears & Compound Ratio

Build multi-stage gear trains by selecting tooth counts for each gear. Gear ratio Ο‰β‚‚/ω₁ = βˆ’N₁/Nβ‚‚ (the sign encodes direction reversal). An idler gear in the chain leaves the overall ratio unchanged but reverses output direction. Compound gear trains multiply individual ratios. Animated tooth meshing shows involute-profile teeth engaging and disengaging in real time.

πŸŽ’

Knapsack — 0/1 DP Table Visualisation with Backtracking

Bottom-up 0/1 knapsack fills the K[i][w] table cell by cell with step-by-step animation. Selected items are highlighted via backtracking from K[n][W]. A side panel compares the DP solution with greedy (value/weight ratio) and shows where greedy fails. Complexity label O(nW) vs O(2ⁿ) brute force updates with n and W sliders.

🌳

Huffman Coding — Priority-Queue Tree Construction & Entropy Comparison

Enter any text (or use a preset) and watch the min-heap build the Huffman tree bottom-up: extract the two lowest-frequency nodes, merge them, re-insert. The resulting prefix-free code table is displayed with code lengths. A live graph plots average code length vs Shannon entropy H = βˆ’Ξ£ p logβ‚‚ p, showing Huffman achieves optimality within 1 bit.

πŸŒ€ Fourier Epicycles β€” Drawing with the DFT

From drawn path to complex DFT

When you draw a closed curve on the canvas the simulator samples it at N equally-spaced parameter values, treating each sample point as a complex number z_n = x_n + iΒ·y_n. The complex Discrete Fourier Transform is computed:

X[k] = Ξ£_{n=0}^{N-1}  z_n Β· exp(βˆ’2Ο€iΒ·kΒ·n / N)    for k = 0, 1, …, Nβˆ’1

Each bin k represents one epicycle:
  centre frequency : k  rotations per full drawing period
  amplitude        : |X[k]| / N
  initial phase    : arg(X[k])

Reconstruction (partial sum of M epicycles):
  z(t) = (1/N) Β· Ξ£_{k=0}^{M-1}  X[k] Β· exp(2Ο€iΒ·kΒ·t / N)

The simulation sorts epicycles by decreasing amplitude so that the largest circles are drawn first. As M increases from 1 to N/2, the partial sum path converges to the original drawing. A slider lets you watch the approximation improve circle by circle.

Why this differs from the Fourier Series simulator

The platform's existing fourier-series simulator applies a 1D DFT to periodic waveforms (square, sawtooth, triangle) and visualises harmonic amplitudes on a bar chart. The fourier-epicycles simulator uses a 2D complex DFT on an arbitrary drawn path β€” both x(t) and y(t) are encoded together as a single complex signal. This means every frequency bin contributes a 2D circular motion (an epicycle), not just a vertical oscillation. The set of rotating circles is a visual proof that any continuous closed curve can be decomposed into circular motions.

Built-in presets and their spectra

The five presets were chosen to illustrate different spectral profiles:

Rendering and performance

Computing a full DFT at N = 512 costs O(NΒ²) multiplications. The simulator uses the Cooley-Tukey radix-2 FFT algorithm (padding to the next power of two) to reduce this to O(N log N):

// Iterative Cooley-Tukey in-place FFT
function fft(x):   // x: complex array, length must be power of 2
    N = x.length
    bit-reverse permutation of x
    for s = 1 to log2(N):
        m = 2^s
        w_m = exp(-2Ο€i / m)
        for k = 0 to N-1 step m:
            w = 1
            for j = 0 to m/2 - 1:
                t = w * x[k + j + m/2]
                u = x[k + j]
                x[k + j]       = u + t
                x[k + j + m/2] = u - t
                w = w * w_m

The epicycle animation renders at 60 fps by recomputing only the tip positions of the rotating arms each frame (no FFT per frame β€” the DFT is computed once on mouse-up). The trailing path is drawn to an off-screen canvas and composited, keeping the main loop light.

🧲 Magnetic Pendulum β€” Chaos and Fractal Basins

Equations of motion

The pendulum bob is modelled as a point mass m suspended on a string of length L, constrained to move in the horizontal plane directly above the three magnets. The height z of the bob above the magnet plane is kept fixed at h (the rest height). The equations of motion in Cartesian coordinates (x, y) are:

Forces on the bob:
  Gravity restoring:  F_g = βˆ’(mg/L) Β· (x, y)   (small-angle approximation)
  Damping:            F_d = βˆ’b Β· (αΊ‹, ẏ)
  Magnetic (magnet i at position r_i = (x_i, y_i)):
    d_i = sqrt((x βˆ’ x_i)Β² + (y βˆ’ y_i)Β² + hΒ²)
    F_m_i = ΞΌ Β· (r_i βˆ’ r) / d_iΒ³

Full ODE:
  ẍ = βˆ’(g/L)Β·x  βˆ’  bΒ·αΊ‹  +  Ξ£_i  ΞΌΒ·(x_i βˆ’ x) / d_iΒ³
  ΓΏ = βˆ’(g/L)Β·y  βˆ’  b·ẏ  +  Ξ£_i  ΞΌΒ·(y_i βˆ’ y) / d_iΒ³

Default parameters: L = 0.5 m, h = 0.05 m, b = 0.2 (damping), ΞΌ = 1.0 (magnetic strength). The three magnets sit at angles 0Β°, 120Β°, 240Β° on a circle of radius 0.3 m.

RK4 integration

Each trajectory is integrated using the classical 4th-order Runge-Kutta method with a fixed time step Ξ”t = 0.01 s. The state vector is s = (x, y, αΊ‹, ẏ):

k1 = f(t,       s)
k2 = f(t + h/2, s + hΒ·k1/2)
k3 = f(t + h/2, s + hΒ·k2/2)
k4 = f(t + h,   s + hΒ·k3)
s_{n+1} = s_n + (h/6)Β·(k1 + 2k2 + 2k3 + k4)

The simulation halts when the bob speed drops below 0.001 m/s for 50 consecutive steps, recording which magnet is nearest as the "captured" magnet. For the basin map a grid of starting positions (up to 400Γ—400 = 160,000 trajectories) is computed using Web Workers for chunked rendering so the UI stays responsive.

Fractal basin boundaries

The resulting basin map is a canonical example of a fractal. At every apparent boundary between two colour regions there is a third region nested infinitely deep β€” the boundaries have Hausdorff dimension strictly greater than 1. This is a direct consequence of sensitivity to initial conditions (positive Lyapunov exponents) near the separatrices. Zooming into the boundary reveals self-similar fine structure at every scale, making this one of the most visually accessible chaos demonstrations on the platform.

Two interactive controls are provided: a "magnetic strength" slider that weakens or strengthens the magnets relative to gravity, and a "damping" slider. At very low damping the bob wanders much longer before settling, producing sharper and deeper fractal structure. At very high damping it falls almost immediately into the nearest magnet, simplifying the basin boundaries toward straight Voronoi lines.

πŸ’§ Surface Wetting β€” From Young's Equation to the Lotus Effect

Young's equation (ideal flat surface)

When a liquid droplet rests on a flat solid surface in a vapour environment, three interfacial tensions act at the contact line: solid-vapour (Ξ³_SV), solid-liquid (Ξ³_SL), and liquid-vapour (Ξ³_LV). Mechanical equilibrium along the surface requires:

Young's equation:
  Ξ³_SV = Ξ³_SL + Ξ³_LV Β· cos(ΞΈ_Y)

  β†’ cos(ΞΈ_Y) = (Ξ³_SV βˆ’ Ξ³_SL) / Ξ³_LV

Contact angle regimes:
  ΞΈ_Y = 0Β°         : complete wetting (liquid spreads indefinitely)
  0Β° < ΞΈ_Y < 90Β°   : hydrophilic (water wets the surface)
  90Β° < ΞΈ_Y < 150Β° : hydrophobic (water beads up)
  ΞΈ_Y > 150Β°        : superhydrophobic (lotus effect)

The animated droplet cross-section morphs continuously with the contact angle slider. The three interface arrows update direction and label to show the force balance.

Wenzel model (rough surface)

Real surfaces are never perfectly flat. Wenzel (1936) introduced a roughness factor r (ratio of actual surface area to projected area, always r β‰₯ 1) into the energy balance:

Wenzel equation:
  cos(ΞΈ_W) = r Β· cos(ΞΈ_Y)

  Consequence:
    If ΞΈ_Y < 90Β°  (hydrophilic): roughness makes it more wetting  β†’ ΞΈ_W < ΞΈ_Y
    If ΞΈ_Y > 90Β°  (hydrophobic): roughness makes it less wetting  β†’ ΞΈ_W > ΞΈ_Y

Roughness factor examples:
  Polished metal:  r β‰ˆ 1.05
  Sand-blasted:    r β‰ˆ 1.5 – 2.0
  Lotus leaf:      r β‰ˆ 2.5 (microscale + nanoscale dual texture)

The surface roughness visualisation shows a magnified cross-section of the substrate with adjustable bump amplitude and spatial frequency. The droplet contact angle updates in real time as both ΞΈ_Y and r are varied.

Cassie-Baxter model and the lotus effect

When a surface has trapped air pockets (composite interface), the droplet rests partly on solid and partly on air. Cassie and Baxter (1944) derived the contact angle for this heterogeneous surface:

Cassie-Baxter equation:
  cos(ΞΈ_CB) = f₁ Β· cos(θ₁) + fβ‚‚ Β· cos(ΞΈβ‚‚)

For solid-air composite (fβ‚‚ = air fraction, ΞΈβ‚‚ = 180Β°):
  cos(ΞΈ_CB) = f_s Β· cos(ΞΈ_Y) βˆ’ (1 βˆ’ f_s)

  where f_s = fractional solid contact area

Lotus leaf: f_s β‰ˆ 0.05 (only 5% solid contact)
  cos(ΞΈ_CB) β‰ˆ 0.05 Β· cos(105Β°) βˆ’ 0.95 β‰ˆ βˆ’0.963
  ΞΈ_CB β‰ˆ 164Β°  (superhydrophobic)

The simulator provides a toggle between Wenzel state (droplet penetrates roughness grooves) and Cassie-Baxter state (droplet sits on top of roughness peaks with air below). The Cassie-to-Wenzel transition (wetting transition) can be triggered by pressing the droplet down, modelled by crossing an energy barrier.

βš™οΈ Gear Train β€” Ratios, Torque and Compound Stages

Gear ratio fundamentals

Two meshing external spur gears with tooth counts N₁ and Nβ‚‚ rotate at angular velocities ω₁ and Ο‰β‚‚. Because their pitch circles roll without slipping, the tangential velocity at the mesh point is equal:

Pitch circle radii: r₁ = N₁·m/2,  rβ‚‚ = Nβ‚‚Β·m/2  (m = module = tooth size)

No-slip mesh condition: r₁·ω₁ = rβ‚‚Β·Ο‰β‚‚

  β†’ gear ratio: Ο‰β‚‚/ω₁ = N₁/Nβ‚‚  (magnitude)

Direction: external mesh reverses rotation β†’ Ο‰β‚‚/ω₁ = βˆ’N₁/Nβ‚‚
           internal mesh (ring gear) preserves rotation

Torque ratio (ideal, no friction):
  Tβ‚‚/T₁ = Nβ‚‚/N₁   (torque multiplied by same factor as speed is divided)

Idler gears

An idler gear (also called a parasite gear) is inserted between a driver and driven gear. It meshes with both but is not connected to any load or power source. Because it introduces two mesh reversals, the net direction of the driven gear is the same as the driver β€” as if no idler were present. The overall gear ratio between first and last gear is unaffected by the idler:

Driver (N₁) β†’ Idler (N_i) β†’ Driven (Nβ‚‚):
  Ο‰_i / ω₁ = βˆ’N₁/N_i       (first mesh, reversed)
  Ο‰β‚‚ / Ο‰_i = βˆ’N_i/Nβ‚‚       (second mesh, reversed again)
  Ο‰β‚‚ / ω₁  = N₁/Nβ‚‚         (idler tooth count cancels)

Practical use: reverse output shaft direction without changing ratio

Compound gear trains

A compound gear train has two or more gears sharing a shaft (keyed together, rotating at the same angular velocity). The total ratio is the product of the individual stage ratios:

3-stage compound train:
  Stage 1: driver N₁ meshes with gear Nβ‚‚ (on shaft A)
  Stage 2: gear N₃ (on shaft A) meshes with gear Nβ‚„ (on shaft B)
  Stage 3: gear Nβ‚… (on shaft B) meshes with output N₆

  Total ratio: ω₆/ω₁ = (N₁·N₃·Nβ‚…) / (Nβ‚‚Β·Nβ‚„Β·N₆)

Example: N₁=10, Nβ‚‚=40, N₃=12, Nβ‚„=60, Nβ‚…=15, N₆=75
  Ratio = (10Β·12Β·15)/(40Β·60Β·75) = 1800/180000 = 1/100
  Input speed 1000 rpm β†’ output 10 rpm, torque Γ—100

The animation renders each gear with a simplified involute tooth profile. Tooth engagement is tracked by monitoring the contact point position along the line of action, and the teeth animate smoothly in and out of mesh. An efficiency panel shows the approximate power loss from gear-mesh friction (typically 1–3% per stage for well-lubricated spur gears).

πŸŽ’ Knapsack β€” The 0/1 DP Table Exposed

Problem definition and brute-force complexity

Given n items, each with weight w_i and value v_i, and a knapsack of capacity W, find the subset of items with maximum total value subject to total weight ≀ W. Each item can be included at most once (the "0/1" constraint). The brute-force approach tries all 2ⁿ subsets β€” feasible only for n ≀ 20 or so.

Bottom-up dynamic programming

The DP recurrence fills an (n+1) Γ— (W+1) table where K[i][w] = maximum value achievable using the first i items in a knapsack of capacity w:

Base case:
  K[0][w] = 0   for all w   (no items β†’ zero value)

Recurrence (for i = 1…n, w = 0…W):
  if w_i > w:
      K[i][w] = K[i-1][w]            // item i too heavy: skip it
  else:
      K[i][w] = max(
          K[i-1][w],                 // don't take item i
          v_i + K[i-1][w - w_i]     // take item i
      )

Answer: K[n][W]
Complexity: O(nΒ·W) time, O(nΒ·W) space (reducible to O(W) with rolling array)

The simulation renders the complete table, colour-coded by value magnitude. As the animation fills each cell it highlights the two cells being compared (K[i-1][w] and K[i-1][wβˆ’w_i]) with a brief flash, making the recurrence structure immediately visible.

Backtracking to find selected items

Once the table is complete, the actual selected items are recovered by backtracking from K[n][W]:

i = n, w = W
while i > 0 and w > 0:
    if K[i][w] != K[i-1][w]:
        include item i           // cell changed β†’ item i was taken
        w = w βˆ’ w_i
    i = i βˆ’ 1

Selected items are highlighted with a gold border in the item list. A side-by-side comparison panel runs the greedy algorithm (sort by value/weight ratio, greedily fill) and marks any discrepancy β€” for 0/1 knapsack, greedy frequently misses the optimum.

Why DP isn't truly polynomial

The O(nW) complexity is sometimes called "pseudo-polynomial" because W can be exponentially large in the number of bits used to encode it. If W = 2^k, the table has nΒ·2^k cells β€” exponential in the input size. True polynomial-time algorithms for 0/1 knapsack are unknown (it is NP-hard). The simulation makes this concrete with a "bit-length" display: slide W from 100 to 100,000 and watch the table size (and render time) scale linearly with W but exponentially with log W.

🌳 Huffman Coding β€” Optimal Prefix-Free Compression

Building the tree with a min-heap

Given a symbol alphabet with known frequencies (or probabilities), Huffman's algorithm builds an optimal prefix-free binary code:

Input: symbols s_1…s_k with frequencies f_1…f_k

Algorithm:
1. Insert all symbols into a min-priority queue (min-heap), keyed by frequency.
2. While heap size > 1:
     a. Extract two nodes with minimum frequency: left (f_L), right (f_R)
     b. Create internal node with frequency f_L + f_R
     c. Insert internal node back into the heap
3. The remaining node is the root of the Huffman tree.

Code assignment:
  Traverse tree from root: going left appends '0', right appends '1'.
  Each leaf's code = the bit string on the path from root to that leaf.

The simulation animates each extraction and merge step. The growing tree is drawn with frequency labels on every node, and the two extracted nodes glow as they are merged.

Entropy and average code length

Shannon's source coding theorem states that no uniquely decodable code can achieve an average code length shorter than the entropy H of the source:

Shannon entropy:
  H = βˆ’Ξ£_{i=1}^{k}  p_i Β· logβ‚‚(p_i)    (bits per symbol)

Huffman average code length:
  LΜ„ = Ξ£_{i=1}^{k}  p_i Β· len(code_i)

Guarantee:  H ≀ LΜ„ < H + 1

For the optimal block code of length n symbols: LΜ„ β†’ H as n β†’ ∞

The live graph on the right panel plots H (horizontal reference line) and LΜ„ (bar) side by side. As you edit the input text, both values update and the gap between them is labelled in millibits. For natural English text H β‰ˆ 4.1 bits/symbol and Huffman achieves LΜ„ β‰ˆ 4.2 bits/symbol β€” within about 2% of the theoretical minimum using single-symbol codes.

Prefix-free property proof

Any code produced by reading tree paths is automatically prefix-free: no codeword is a prefix of another, because codewords only appear at leaves and no leaf is an ancestor of another leaf. This guarantees unambiguous decoding without any separator characters. The simulator includes a decoder panel β€” type in a bit string and it parses it symbol by symbol, highlighting the tree path taken for each decoded symbol.

Up Next

Wave 67 is already built and brings the library to a landmark milestone: the 600-simulation mark. The six additions span chaos theory (logistic map bifurcation diagram with Feigenbaum constant), optics (moirΓ© patterns and Nyquist aliasing), computational geometry (Delaunay triangulation), transport modelling (Nagel-Schreckenberg traffic cellular automaton), algorithms (quadtree spatial partitioning), and orbital mechanics (tidal forces with spring/neap tide cycles). Read all about it in Devlog #88.

← Devlog #86: Wave 65 Devlog #88: Wave 67 →