Skip to content
Vamshi Jandhyala

Books · The Fiddler: Solutions

Chapter 67

Can You Squeeze the Heart?

Build a heart by attaching a semicircle of radius 12\tfrac12 to each of two adjacent edges of a unit square, both bulging outward. What is the radius of the smallest circle that contains the heart?

The Fiddler, Zach Wissner-Gross, February 14, 2025(original post)

The heart and its smallest enclosing circle, centred on the symmetry diagonal. The binding points are the tip and the two lobe apexes.

Solution

Place the square as [0,1]2[0,1]^2 with lobes on the top and right edges; the figure is symmetric about the diagonal y=xy=x, so the enclosing circle is centred at some (c,c)(c,c). Two kinds of point fight to be farthest: the tip at the opposite corner (0,0)(0,0), at distance c2c\sqrt2, and a lobe apex, at distance (c12)2+(c1)2+12\sqrt{(c-\tfrac12)^2+(c-1)^2}+\tfrac12 (centre-to-lobe plus the lobe radius). The optimum balances them: c2=(c12)2+(c1)2+12.c\sqrt2=\sqrt{\bigl(c-\tfrac12\bigr)^2+(c-1)^2}+\tfrac12. Squaring and simplifying collapses the quadratic terms and leaves (32)c=1(3-\sqrt2)\,c=1, so c=132c=\tfrac{1}{3-\sqrt2} and R=c2=232=32+270.8918.R=c\sqrt2=\frac{\sqrt2}{3-\sqrt2}=\frac{3\sqrt2+2}{7}\approx\boxed{0.8918}.

The computation

Build the heart’s boundary as points, square edges plus the two semicircular arcs, then find the centre that minimises the farthest distance (the smallest enclosing circle). Its radius matches 32+27\tfrac{3\sqrt2+2}{7}.

import numpy as np
from scipy.optimize import minimize
P = [(t, 0) for t in np.linspace(0, 1, 200)] + [(0, t) for t in np.linspace(0, 1, 200)]
P += [(0.5 + 0.5*np.cos(a), 1 + 0.5*np.sin(a)) for a in np.linspace(0, np.pi, 200)]
P += [(1 + 0.5*np.cos(a), 0.5 + 0.5*np.sin(a)) for a in np.linspace(-np.pi/2, np.pi/2, 200)]
P = np.array(P)
f = lambda c: np.max(np.hypot(P[:, 0] - c[0], P[:, 1] - c[1]))
print(minimize(f, [0.6, 0.6], method='Nelder-Mead').fun, (3*np.sqrt(2)+2)/7)  # 0.89181

Extra Credit

What is the radius of the smallest circle containing two non-overlapping hearts?

Solution

This is a genuinely hard non-convex packing, and the official value is paywalled. Two disjoint hearts side by side give the rigorous upper bound 2R1.7842R\approx1.784. Letting them interlock does better: rotating one heart and nesting it against the other, a numerical search settles on an enclosing circle of radius about R21.50,R_2\lesssim\boxed{1.50}, a valid arrangement found by search, not a claimed optimum.

The computation

Model each heart as an interior point cloud (for the no-overlap test) and a boundary point cloud (for the enclosing circle). From many random placements of the second heart, reject any that overlaps the first and keep the smallest enclosing circle of the pair.

def in_heart(P):                                  # interior test, heart at identity
    x, y = P[..., 0], P[..., 1]
    return (((x >= 0) & (x <= 1) & (y >= 0) & (y <= 1))
            | ((y >= 1) & ((x - .5)**2 + (y - 1)**2 <= .25))
            | ((x >= 1) & ((x - 1)**2 + (y - .5)**2 <= .25)))
t = np.linspace(0, 1, 60); a = np.linspace(0, np.pi, 90)
BND = np.vstack([np.c_[t*0, t], np.c_[t, t*0],
                 np.c_[.5 + .5*np.cos(a), 1 + .5*np.sin(a)],
                 np.c_[1 + .5*np.cos(a - np.pi/2), .5 + .5*np.sin(a - np.pi/2)]])
gx, gy = np.meshgrid(np.linspace(-.02, 1.52, 120), np.linspace(-.02, 1.52, 120))
FILL = np.c_[gx.ravel(), gy.ravel()]; FILL = FILL[in_heart(FILL)]; cen = np.array([.5, .5])
def place(pr, Q):
    phi, tx, ty = pr; c, s = np.cos(phi), np.sin(phi)
    return (Q - cen) @ np.array([[c, s], [-s, c]]) + cen + np.array([tx, ty])
enc = lambda pts: minimize(lambda c: np.max(np.hypot(*(pts - c).T)),
                           pts.mean(0), method='Nelder-Mead').fun
rng = np.random.default_rng(0); best = 9e9
for _ in range(150):
    def obj(pr):
        if max(abs(pr[1]), abs(pr[2])) > 3 or in_heart(place(pr, FILL)).any(): return 50.0
        return enc(np.vstack([BND, place(pr, BND)]))
    r = minimize(obj, [rng.uniform(0, 2*np.pi), *rng.uniform(-1.6, 1.6, 2)],
                 method='Nelder-Mead', options={'maxiter': 250, 'xatol': 1e-3, 'fatol': 1e-3})
    if obj(r.x) < 49: best = min(best, r.fun)
print(round(best, 3))                             # 1.497