claude-sonnet-5-catenary-activation-example.js

total 0
used 0
limit 0
/* --- title: Catenary Activation Curve categories: curve catenary activation neural-network files: head stroke pointlist point mouse dragging stage --- A visual companion to the "catenary as a neural-network activation function" idea: two draggable points `A` and `B` define the endpoints of a hanging-chain (catenary) curve. The curve is clamped flat outside `[x1, x2]` and blended into those plateaus with a cubic Hermite ease, so the whole thing behaves like a smooth, sag-tunable activation function rather than an open-ended arc. Controls: drag move point A or B (the curve's endpoints) scroll (away from a point) adjust the chain length / sag amount scroll (over a point) resizes that point's handle, ignore it The catenary math (solveCatenary / catenaryOutput / catenaryDerivative) and the Hermite tip-smoothing formulas are ported near-verbatim from the `catenary.ts` activation-function experiment - see the session notes for the closed-form derivation. Canvas y grows downward, so points are solved in a y-flipped "math space" and flipped back for drawing - this makes the chain sag *down* on screen like a real rope, rather than arching upward. */ /** * Solve the catenary y = a*cosh((x-p)/a) + q that passes exactly through * (x1,y1) and (x2,y2) with the given (slack) chain length, in "math space" * (y increases upward). Assumes x1 <= x2. */ function solveCatenary({ x1, y1, x2, y2, length }) { const MIN_DX = 1e-3 let dx = x2 - x1 if (dx < MIN_DX) { dx = MIN_DX x2 = x1 + dx } const dy = y2 - y1 const straightLineDist = Math.hypot(dx, dy) const chainLength = Math.max(length, straightLineDist * 1.00001) const mSquared = Math.max(chainLength * chainLength - dy * dy, 1e-9) const ratio = Math.sqrt(mSquared) / dx // Solve sinh(u)/u = ratio for u > 0 via bisection. // sinh(u)/u is monotonically increasing from 1 (u->0) so for ratio > 1 // there is exactly one root. let lo = 1e-6, hi = 40 for (let i = 0; i < 80; i++) { const mid = (lo + hi) / 2 const f = Math.sinh(mid) / mid if (f > ratio) { hi = mid } else { lo = mid } } const u = (lo + hi) / 2 const a = dx / (2 * u) const p = (x1 + x2) / 2 - a * Math.asinh(dy / Math.sqrt(mSquared)) const q = y1 - a * Math.cosh((x1 - p) / a) return { a, p, q, x1, y1, x2, y2, length: chainLength, straightLineDist } } function catenaryOutput(x, sol) { return sol.a * Math.cosh((x - sol.p) / sol.a) + sol.q } function catenaryDerivative(x, sol) { return Math.sinh((x - sol.p) / sol.a) } /** Cubic Hermite blend of value/slope over [a, b]. */ function hermite(x, a, b, va, ma, vb, mb) { const t = (x - a) / (b - a) const t2 = t * t, t3 = t2 * t const h00 = 2 * t3 - 3 * t2 + 1 const h10 = t3 - 2 * t2 + t const h01 = -2 * t3 + 3 * t2 const h11 = t3 - t2 return h00 * va + h10 * (b - a) * ma + h01 * vb + h11 * (b - a) * mb } /** * Full activation curve in math space: flat plateau, Hermite ease, catenary * arc, Hermite ease, flat plateau. */ function evaluateActivation(x, sol, margin) { const { x1, y1, x2, y2 } = sol if (x < x1 - margin) return y1 if (x < x1) { const der1 = catenaryDerivative(x1, sol) return hermite(x, x1 - margin, x1, y1, 0, y1, der1) } if (x <= x2) { return catenaryOutput(x, sol) } if (x <= x2 + margin) { const der2 = catenaryDerivative(x2, sol) return hermite(x, x2, x2 + margin, y2, der2, y2, 0) } return y2 } class MainStage extends Stage { canvas = 'playspace' // Chain length = straightLineDistance * slackFactor. 1 == taut. slackFactor = 1.4 minSlack = 1.02 maxSlack = 4 smoothing = 40 // px margin either side used to blend into the plateau flatRun = 90 // px of visible flat plateau beyond the smoothing margin mounted() { this.a = new Point({ x: 260, y: 180, radius: 9 }) this.b = new Point({ x: 560, y: 300, radius: 9 }) this.dragging.addPoints(this.a, this.b) // Only fires when the wheel happens away from a draggable point, // so it never fights with the built-in point-radius scroll resize. this.dragging.onWheelEmpty = this.onWheelEmpty.bind(this) } firstDraw(ctx) { ctx.textAlign = 'left' ctx.textBaseline = 'top' } onWheelEmpty(ev) { const dir = ev.deltaY > 0 ? -1 : 1 this.slackFactor = clamp(this.slackFactor + dir * 0.05, this.minSlack, this.maxSlack) } draw(ctx) { this.clear(ctx) const a = this.a, b = this.b const left = a.x <= b.x ? a : b const right = a.x <= b.x ? b : a const dx = right.x - left.x const straightLineDist = Math.hypot(dx, right.y - left.y) const chainLength = straightLineDist * this.slackFactor // Flip into math space (y-up) so the solved chain sags visually // *downward* on screen once it's flipped back for drawing. const sol = solveCatenary({ x1: left.x, y1: -left.y, x2: right.x, y2: -right.y, length: chainLength }) const m = this.smoothing const xMin = sol.x1 - m - this.flatRun const xMax = sol.x2 + m + this.flatRun this.strokeActivation(ctx, sol, xMin, sol.x1 - m, '#555', true) this.strokeActivation(ctx, sol, sol.x1 - m, sol.x1, '#e0a03c', true) this.strokeActivation(ctx, sol, sol.x1, sol.x2, '#4fd1c5', false) this.strokeActivation(ctx, sol, sol.x2, sol.x2 + m, '#e0a03c', true) this.strokeActivation(ctx, sol, sol.x2 + m, xMax, '#555', true) this.drawGuides(ctx, sol, m) left.pen.indicator(ctx, { color: '#ff5f7e' }) right.pen.indicator(ctx, { color: '#5fa8ff' }) this.drawHud(ctx, sol) } strokeActivation(ctx, sol, xa, xb, color, dashed, steps = 40) { if (xb <= xa) return ctx.save() ctx.setLineDash(dashed ? [6, 5] : []) ctx.strokeStyle = color ctx.lineWidth = 2 ctx.beginPath() for (let i = 0; i <= steps; i++) { const t = i / steps const x = xa + t * (xb - xa) const yCanvas = -evaluateActivation(x, sol, this.smoothing) if (i === 0) { ctx.moveTo(x, yCanvas) } else { ctx.lineTo(x, yCanvas) } } ctx.stroke() ctx.restore() } drawGuides(ctx, sol, margin) { ctx.save() ctx.setLineDash([2, 4]) ctx.strokeStyle = '#444' ctx.lineWidth = 1 const xs = [sol.x1 - margin, sol.x1, sol.x2, sol.x2 + margin] xs.forEach(x => { const y = -evaluateActivation(x, sol, margin) ctx.beginPath() ctx.moveTo(x, y - 14) ctx.lineTo(x, y + 14) ctx.stroke() }) ctx.restore() } drawHud(ctx, sol) { ctx.fillStyle = '#EEE' ctx.font = '400 14px inter, sans-serif' const lines = [ `chain length: ${sol.length.toFixed(1)}px (straight-line: ${sol.straightLineDist.toFixed(1)}px)`, `slack factor: ${this.slackFactor.toFixed(2)}x (scroll canvas to adjust)`, `smoothing margin: ${this.smoothing}px`, `drag the pink / blue points to reshape the curve` ] lines.forEach((line, i) => ctx.fillText(line, 16, 16 + i * 18)) } } ;stage = MainStage.go();
Run
Meta Data
title Catenary Activation Curve
imports ()
files ('head', 'stroke', 'pointlist', 'point', 'mouse', 'dragging', 'stage')
unused_keys ()
unknown_keys ('categories',)
categories ['', 'curve', 'catenary', 'activation', 'neural-network']
filepath_exists True
path claude-sonnet-5-catenary-activation-example.js
filepath claude-sonnet-5-catenary-activation-example.js
clean_files ('../point_src/core/head.js', '../point_src/setunset.js', '../point_src/stroke.js', '../point_src/pointlistdraw.js', '../point_src/pointlistgradient.js', '../point_src/pointlistshape.js', '../point_src/pointlistgenerator.js', '../point_src/unpack.js', '../point_src/pointlist.js', '../point_src/pointlistpen.js', '../point_src/pointpen.js', '../point_src/compass.js', '../point_src/center.js', '../point_src/point-content.js', '../point_src/pointdraw.js', '../point_src/relative-xy.js', '../point_src/pointcast.js', '../point_src/point.js', '../point_src/events.js', '../point_src/automouse.js', '../point_src/functions/clamp.js', '../point_src/distances.js', '../point_src/protractor.js', '../point_src/text/beta.js', '../point_src/dragging.js', '../point_src/stage-hooks.js', '../point_src/stage-resize.js', '../point_src/functions/resolve.js', '../point_src/stage.js')
markdown {'html': '<p>A visual companion to the "catenary as a neural-network activation function"\nidea: two draggable points <code>A</code> and <code>B</code> define the endpoints of a hanging-chain\n(catenary) curve. The curve is clamped flat outside <code>[x1, x2]</code> and blended\ninto those plateaus with a cubic Hermite ease, so the whole thing behaves\nlike a smooth, sag-tunable activation function rather than an open-ended arc.</p>\n<p>Controls:\n drag move point A or B (the curve\'s endpoints)\n scroll (away from a point) adjust the chain length / sag amount\n scroll (over a point) resizes that point\'s handle, ignore it</p>\n<p>The catenary math (solveCatenary / catenaryOutput / catenaryDerivative) and\nthe Hermite tip-smoothing formulas are ported near-verbatim from the\n<code>catenary.ts</code> activation-function experiment - see the session notes for the\nclosed-form derivation. Canvas y grows downward, so points are solved in a\ny-flipped "math space" and flipped back for drawing - this makes the chain\nsag <em>down</em> on screen like a real rope, rather than arching upward.</p>', 'content': '---\ntitle: Catenary Activation Curve\ncategories:\n curve\n catenary\n activation\n neural-network\nfiles:\n head\n stroke\n pointlist\n point\n mouse\n dragging\n stage\n---\n\nA visual companion to the "catenary as a neural-network activation function"\nidea: two draggable points `A` and `B` define the endpoints of a hanging-chain\n(catenary) curve. The curve is clamped flat outside `[x1, x2]` and blended\ninto those plateaus with a cubic Hermite ease, so the whole thing behaves\nlike a smooth, sag-tunable activation function rather than an open-ended arc.\n\nControls:\n drag move point A or B (the curve\'s endpoints)\n scroll (away from a point) adjust the chain length / sag amount\n scroll (over a point) resizes that point\'s handle, ignore it\n\nThe catenary math (solveCatenary / catenaryOutput / catenaryDerivative) and\nthe Hermite tip-smoothing formulas are ported near-verbatim from the\n`catenary.ts` activation-function experiment - see the session notes for the\nclosed-form derivation. Canvas y grows downward, so points are solved in a\ny-flipped "math space" and flipped back for drawing - this makes the chain\nsag *down* on screen like a real rope, rather than arching upward.'}