Gabriel’s horn
Can a solid with an infinite surface have a finite volume? The hyperbola \(y = 1/x\), for \(x\) from 1 on, turned about the \(x\) axis, bounds such a solid, Gabriel’s horn. Torricelli showed that its volume is \(\pi\), though it is infinitely long, and its surface is infinite. The horn could be filled with \(\pi\) of paint, but no amount of paint would cover its surface, which is the painter’s paradox.
The example cuts the horn off at \(x = A\), for \(A\) from 10 to 1000. The hyperbola is a conic, so one rational quadratic B-spline traces it exactly. The region under it must have the area \(\ln A\). Revolved, its volume must be \(\pi(1 - 1/A)\), its surface the integral \(2\pi \int \tfrac{1}{x} \sqrt{1 + 1/x^4}\,dx\), and the surface must grow without bound, like \(2\pi \ln A\).

[1]:
import math
import netgen.occ as occ
from scipy.integrate import quad
from scipy.optimize import brentq
import planex as px
The hyperbola
A rational quadratic B-spline from \(P_0\) to \(P_2\), its middle pole \(P_1\) where the tangents at the two ends cross, traces a conic, and the middle weight \(w\) says which one. On the line from the chord’s middle \(M\) to \(P_1\), the curve passes through the point \(M + \tfrac{w}{1 + w}\,(P_1 - M)\), so the weight follows from where that line meets the hyperbola, \(s = w/(1 + w)\):
[3]:
def hyperbola(x0, x1):
"""y = 1/x from x0 to x1 as one rational quadratic B-spline"""
p0, p2 = (x0, 1 / x0), (x1, 1 / x1)
p1 = (2 * x0 * x1 / (x0 + x1), 2 / (x0 + x1)) # where the tangents at both ends cross
m = ((p0[0] + p2[0]) / 2, (p0[1] + p2[1]) / 2)
s = brentq(lambda s: (m[0] + s * (p1[0] - m[0])) * (m[1] + s * (p1[1] - m[1])) - 1, 0, 1)
return px.BSpline([p0, p1, p2], [0, 0, 0, 1, 1, 1], 2, weights=[1, s / (1 - s), 1])
def region(A):
"""the region under the hyperbola from 1 to A"""
return px.Shape([px.Line((1, 0), (A, 0)), px.Line((A, 0), (A, 1 / A)), hyperbola(1, A), px.Line((1, 1), (1, 0))]).Face()
for A in (10, 100, 1000):
print("A", A, "| area under the hyperbola", region(A).mass, "ln A", math.log(A))
A 10 | area under the hyperbola 2.302585092994041 ln A 2.302585092994046
A 100 | area under the hyperbola 4.605170185988089 ln A 4.605170185988092
A 1000 | area under the hyperbola 6.907755278981624 ln A 6.907755278982137
The region from 1 to 10 is turned about the \(x\) axis into the horn (Figure 1):
[4]:
horn = region(10).ToOCCFace().Revolve(occ.Axis((0, 0, 0), occ.X), 360)
(a) The region under y = 1/x
(b) Turned about the x axis
Volume and surface
The horn’s volume is \(\pi \int (1/x)^2\,dx = \pi(1 - 1/A)\). Its surface, without the two end discs, is the face whose area is largest. Both are compared with their integrals, and the surface also with \(2\pi \ln A\), its lower bound:
[6]:
for A in (10, 100, 1000):
solid = region(A).ToOCCFace().Revolve(occ.Axis((0, 0, 0), occ.X), 360)
side = max(f.mass for f in solid.faces)
exact = 2 * math.pi * quad(lambda x: math.sqrt(1 + x**-4) / x, 1, A, limit=200)[0]
print("A", A, "| volume", solid.mass, "exact", math.pi * (1 - 1 / A))
print(" surface", side, "exact", exact, "| less 2 pi ln A", side - 2 * math.pi * math.log(A))
A 10 | volume 2.827431804642006 exact 2.827433388230814
surface 15.1774498748198 exact 15.177449874819796 | less 2 pi ln A 0.7098810499888693
A 100 | volume 3.110173819008999 exact 3.1101767270538954
surface 29.645097230630824 exact 29.64509723063139 | less 2 pi ln A 0.7099595809689632
A 1000 | volume 3.138446737738036 exact 3.1384510609362035
surface 44.11266606331226 exact 44.112666063315494 | less 2 pi ln A 0.7099595888194727
The volumes agree with \(\pi(1 - 1/A)\) to about six digits, the surfaces with their integral to all digits. The volume settles toward \(\pi\), while the surface keeps the same lead over \(2\pi \ln A\), about 0.71, and grows with \(\ln A\) without bound. Each factor of 10 in length adds \(2\pi \ln 10\), about 14.5, to the surface and almost nothing to the volume.