The fastest slide
Which track between two points does a bead slide down fastest, from rest and without friction? This is the brachistochrone problem, posed by Johann Bernoulli. The straight ramp is the shortest, but the bead starts slowly on it, and a circular arc is faster. The fastest track is a cycloid, the curve that a point on a rolling circle traces. The cycloid is also the tautochrone, the curve that Huygens used for his pendulum clocks. Wherever on it the bead starts from rest, it takes the same time to the bottom.
The example slides a bead down three tracks from (0, 0) to \((\pi r, -2r)\), for a rolling circle of radius \(r = 1\,\text{m}\). The cycloid must take \(\pi \sqrt{r/g}\), less than the others, from any height on it, and its length must be \(4r\). Swept with a disc, it is a tube whose volume must be \(\pi \rho^2 L\).

[1]:
import math
import numpy as np
import netgen.occ as occ
import planex as px
Three tracks
The cycloid is \(x = r\,(\theta - \sin\theta)\), \(y = -r\,(1 - \cos\theta)\), from its cusp at \(\theta = 0\) to its bottom at \(\theta = \pi\). px.Curve draws it from this formula, with \(\theta\) in degrees, t*pi/180 where it stands alone. The arc is the circle through both ends that is level at the bottom, as the cycloid is, so its center lies straight above the bottom end, at the height \((\pi^2 - 4)\,r/4\) (Figure 1). The cycloid’s length is \(4r\),
which the print compares:
[3]:
g = 9.81
r = 1.0
def cycloid(theta0):
"""the cycloid from theta0, in radians, to its bottom at pi"""
return px.Curve((f"{r}*(t*pi/180 - sin(t))", f"-{r}*(1 - cos(t))"), (math.degrees(theta0), 180))
top, bottom = (0.0, 0.0), (math.pi * r, -2 * r)
tracks = {
"ramp": px.Line(top, bottom),
"arc": px.Arc(top, bottom, (math.pi * r, (math.pi**2 - 4) * r / 4)),
"cycloid": cycloid(0),
}
print("lengths", {name: track.mass for name, track in tracks.items()}, "| 4r", 4 * r)
lengths {'ramp': 3.724191778237173, 'arc': 3.9314191519790076, 'cycloid': 3.999999999999859} | 4r 4.0
Sliding down
The bead’s speed follows from its energy, \(v = \sqrt{2 g h}\) after a drop \(h\). slide follows a track’s points, Polylines at a tolerance of 10⁻⁷, and adds the time of every straight piece between them. On a straight piece the bead’s speed changes linearly in time, so it takes \(2L/(v_1 + v_2)\) for its length \(L\), exactly. The points follow the curve closely enough for the sum to be the curve’s time. slide also returns the times at the points, for the picture
below. The ramp’s time is \(L \sqrt{2/(g h)}\), and the cycloid’s is \(\pi \sqrt{r/g}\), the shortest of the three:
[5]:
def slide(track):
"""the points along the track and the times the bead, starting there at rest, reaches them"""
points = np.asarray(track.Polylines(1e-7)[0])[:, :2]
speed = np.sqrt(np.maximum(2 * g * (points[0, 1] - points[:, 1]), 0))
pieces = np.hypot(*np.diff(points, axis=0).T)
return points, np.concatenate([[0], np.cumsum(2 * pieces / (speed[:-1] + speed[1:]))])
slides = {name: slide(track) for name, track in tracks.items()}
for name, (points, times) in slides.items():
print(name, "|", times[-1], "s")
print("ramp", math.dist(top, bottom) * math.sqrt(2 / (g * 2 * r)), "s | cycloid pi sqrt(r/g)", math.pi * math.sqrt(r / g), "s")
ramp | 1.1890429254666885 s
arc | 1.0149483026825508 s
cycloid | 1.0030333599965362 s
ramp 1.1890429254666885 s | cycloid pi sqrt(r/g) 1.0030333403553238 s
The next figure shows the beads at the same instants, every 0.2 s. The cycloid’s bead drops steeply at first, gains speed early and is ahead all the way.

From any height
On the tautochrone, the bead reaches the bottom in the same time from any point where it starts from rest. From \(\theta = 0^\circ\), \(45^\circ\), \(90^\circ\) and \(135^\circ\), the times to the bottom are compared with \(\pi \sqrt{r/g}\):
[8]:
for degrees in (0, 45, 90, 135):
points, times = slide(cycloid(math.radians(degrees)))
print("from", degrees, "degrees |", times[-1], "s, pi sqrt(r/g)", math.pi * math.sqrt(r / g), "s")
from 0 degrees | 1.0030333599965362 s, pi sqrt(r/g) 1.0030333403553238 s
from 45 degrees | 1.0030340444734425 s, pi sqrt(r/g) 1.0030333403553238 s
from 90 degrees | 1.0030347685300822 s, pi sqrt(r/g) 1.0030333403553238 s
from 135 degrees | 1.0030386546497416 s, pi sqrt(r/g) 1.0030333403553238 s
A track in 3D
A tube of radius \(\rho = 5\,\text{cm}\) along the cycloid is a disc on the plane square to the curve at its start, swept along it with occ.Pipe (Figure 2). Near the cusp the cycloid bends more sharply than the tube is thick, so the tube starts at \(\theta = 0.3\), where the curve’s radius of curvature, \(4r \sin(\theta/2)\), is well above \(\rho\). The disc’s center follows the curve, so the volume is the disc’s area times the length,
\(\pi \rho^2 \cdot 4r \cos(\theta/2)\):
[9]:
start = 0.3
spine = cycloid(start)
x0, y0 = r * (start - math.sin(start)), -r * (1 - math.cos(start))
dx, dy = 1 - math.cos(start), -math.sin(start)
square = px.Plane(origin=(x0, y0, 0), normal=(dx / math.hypot(dx, dy), dy / math.hypot(dx, dy), 0))
tube = occ.Pipe(spine.ToOCCWire(), px.QuickSketch(square).Circle(0.05).Face().ToOCCFace())
print("tube", tube.mass, "m³, exact", math.pi * 0.05**2 * 4 * r * math.cos(start / 2))
tube 0.031063219298310796 m³, exact 0.031063159545259313
See Lines and arcs for px.Arc and Splines for splines through points.